diff --git a/Documentation.md b/Documentation.md index c69db01..e2e5d9c 100644 --- a/Documentation.md +++ b/Documentation.md @@ -3,7 +3,7 @@ ## Requirements - NET .Core framework -- MongoDb +- PostgreSQL database - Node.JS - Discord server with API keys - Alchemy Keys diff --git a/README.md b/README.md index 6f1e3ec..b05f996 100644 --- a/README.md +++ b/README.md @@ -653,3 +653,23 @@ npm run prepare-code For more details, see the [scripts documentation](scripts/README.md). +# Entity Framework Core Migrations + +To manage database schema changes for the backend, use the following EF Core commands from the project root: + +## Add a Migration + +``` +dotnet ef migrations add --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 `` 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. + diff --git a/scripts/README.md b/scripts/README.md index 708ed52..8cb5b03 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,191 +1,63 @@ -# Scripts +# 🚀 Safe Database Migration Script -This directory contains utility scripts for the project. +A clean, focused script for safe database migrations with automatic backup and cleanup. -## Add JS Extensions Script +## 📋 Features -The `add-js-extensions.mjs` script adds `.js` extensions to import statements in JavaScript and TypeScript files, and also adds the required JSON import assertions. +- ✅ **Connectivity Check**: Verifies database connection before proceeding +- ✅ **Automatic Backup**: Creates backup with retry logic (3 attempts) +- ✅ **Safety First**: Exits if backup fails - no migration without backup +- ✅ **Smart PostgreSQL**: Uses Docker PostgreSQL client if `psql` not available +- ✅ **Migration**: Runs Entity Framework migrations +- ✅ **Verification**: Checks migration success +- ✅ **Cleanup**: Keeps only last 5 backups per environment -### Why This Script? - -When working with ES Modules in Node.js: -1. Imports of local files require explicit file extensions -2. JSON imports require an `assert { type: 'json' }` assertion - -This script automates both processes to ensure your code is ESM-compatible. - -### Usage - -Run the script with npm: +## 🛠️ Usage ```bash -# Fix imports in the src directory (default) -npm run fix-imports +# Development environment +./safe-migrate.sh Development -# Fix imports in a specific directory -npm run fix-imports-dir -- path/to/directory +# Sandbox environment +./safe-migrate.sh Sandbox + +# Production environment +./safe-migrate.sh Production ``` -Or run the script directly: +## 🔄 Process Flow -```bash -# Fix imports in the src directory (default) -node scripts/add-js-extensions.mjs +1. **Environment Validation**: Validates environment parameter +2. **Connectivity Check**: Tests database connection +3. **Backup Creation**: Creates backup with retry logic +4. **Migration**: Runs pending migrations +5. **Verification**: Checks migration success +6. **Cleanup**: Removes old backups (keeps last 5) -# Fix imports in a specific directory -node scripts/add-js-extensions.mjs path/to/directory +## 🚨 Safety Features + +- **Mandatory Backup**: Script exits if backup cannot be created +- **Retry Logic**: 3 backup attempts with 5-second delays +- **Error Handling**: Clear error messages and exit codes +- **Logging**: Detailed logs for troubleshooting + +## 📊 Output + +``` +✅ Database connectivity test passed +✅ Database backup created: ./backups/managing_Development_backup_20250726_043047.sql +✅ Database migration completed successfully +✅ Database schema verification passed +✅ Kept last 5 backups for Development environment ``` -### What This Script Does +## 🔧 Prerequisites -1. Recursively scans all JavaScript and TypeScript files in the specified directory -2. Identifies import statements with relative paths (starting with `./` or `../`) that don't have extensions and adds `.js` extensions -3. Identifies JSON imports that are missing the required assertion and adds `assert { type: 'json' }` -4. Provides a summary of files modified and any errors encountered +- Docker (for PostgreSQL client) +- .NET 8.0 SDK +- Access to target database -### Examples +## 📁 Files -Before: -```javascript -import { bigMath } from "./bigmath"; -import data from "./data.json"; -``` - -After: -```javascript -import { bigMath } from "./bigmath.js"; -import data from "./data.json" assert { type: 'json' }; -``` - -### Important Notes - -- The script only modifies imports with relative paths (starting with `./` or `../`) -- It skips imports that already have a file extension (except for JSON files) -- It adds `.js` extensions to extensionless imports -- It adds `assert { type: 'json' }` to JSON imports that don't already have it -- It handles regular imports, dynamic imports, and exports - -## Remove JSON Assertions Script - -The `remove-json-assertions.mjs` script removes `assert { type: 'json' }` assertions from JSON imports. - -### Why This Script? - -Different JavaScript environments have different requirements for JSON imports: -1. Some newer environments require the `assert { type: 'json' }` assertion -2. Others don't support or need these assertions - -This script removes these assertions to improve compatibility with environments that don't need them. - -### Usage - -Run the script with npm: - -```bash -# Remove JSON assertions -npm run remove-json-assertions - -# Run both import fixes and assertion removal in one command -npm run prepare-code -``` - -Or run the script directly: - -```bash -node scripts/remove-json-assertions.mjs -``` - -### What This Script Does - -1. Recursively scans all JavaScript and TypeScript files in the project -2. Identifies JSON imports with `assert { type: 'json' }` assertions -3. Removes the assertions while preserving the import statements -4. Provides a summary of files modified - -### Examples - -Before: -```javascript -import data from "./data.json" assert { type: 'json' }; -``` - -After: -```javascript -import data from "./data.json"; -``` - -### Important Notes - -- The script only modifies JSON imports with assertions -- It preserves all other import statements -- It works in conjunction with the add-js-extensions script -- These scripts can be run in sequence to first fix imports then remove assertions - -## Update JSON Imports Script - -The `update-json-imports.mjs` script updates JSON imports to use the modern `with { type: "json" }` syntax. - -### Why This Script? - -Different JavaScript environments have different requirements for JSON imports: -1. Older environments used `assert { type: 'json' }` for JSON imports -2. Modern JavaScript environments now use the `with { type: "json" }` syntax - -This script updates your codebase to use the newer, more standardized approach. - -### Usage - -Run the script with npm: - -```bash -# Update JSON import syntax -npm run update-json-imports - -# Run both import fixing and JSON import updating in one command -npm run prepare-code -``` - -Or run the script directly: - -```bash -node scripts/update-json-imports.mjs -``` - -### What This Script Does - -1. Recursively scans all JavaScript and TypeScript files in the project -2. Identifies JSON imports using either: - - The older `assert { type: 'json' }` syntax - - No type assertion at all - - Erroneous dual syntax (`assert { type: 'json' } with { type: "json" }`) -3. Updates them to use the modern `with { type: "json" }` syntax -4. Provides a summary of files modified - -### Examples - -Before (old assert syntax): -```javascript -import data from "./data.json" assert { type: 'json' }; -``` - -Before (no type assertion): -```javascript -import data from "./data.json"; -``` - -Before (erroneous dual syntax): -```javascript -import data from "./data.json" assert { type: 'json' } with { type: "json" }; -``` - -After (in all cases): -```javascript -import data from "./data.json" with { type: "json" }; -``` - -### Important Notes - -- The script updates all JSON imports to use the modern syntax -- It properly fixes cases where both old and new syntax are present -- It preserves all other import statements -- Files with properly formatted imports are not modified \ No newline at end of file +- **Backups**: `./backups/managing_[Environment]_backup_[Timestamp].sql` +- **Logs**: `./migration_[Environment]_[Timestamp].log` diff --git a/scripts/safe-migrate.sh b/scripts/safe-migrate.sh new file mode 100755 index 0000000..5edf38d --- /dev/null +++ b/scripts/safe-migrate.sh @@ -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 "==========================================" \ No newline at end of file diff --git a/src/Managing.Api.Workers/Controllers/WorkerController.cs b/src/Managing.Api.Workers/Controllers/WorkerController.cs index 3a5bc48..ca2574e 100644 --- a/src/Managing.Api.Workers/Controllers/WorkerController.cs +++ b/src/Managing.Api.Workers/Controllers/WorkerController.cs @@ -18,9 +18,10 @@ public class WorkerController : ControllerBase } [HttpGet] - public ActionResult> GetWorkers() + public async Task>> GetWorkers() { - return Ok(_workerService.GetWorkers()); + var workers = await _workerService.GetWorkers(); + return Ok(workers.ToList()); } [HttpPatch] diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs index f442c1a..b3507c1 100644 --- a/src/Managing.Api.Workers/Program.cs +++ b/src/Managing.Api.Workers/Program.cs @@ -6,10 +6,10 @@ using Managing.Bootstrap; using Managing.Common; using Managing.Core.Middleawares; using Managing.Infrastructure.Databases.InfluxDb.Models; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Configurations; +using Managing.Infrastructure.Databases.PostgreSql; using Managing.Infrastructure.Evm.Models.Privy; using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.OpenApi.Models; using NSwag; @@ -27,9 +27,9 @@ builder.Configuration.SetBasePath(AppContext.BaseDirectory); builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json"); -var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"]; var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; +var postgreSqlConnectionString = builder.Configuration.GetSection("PostgreSql")["ConnectionString"]; // Initialize Sentry SentrySdk.Init(options => @@ -61,7 +61,6 @@ builder.Services.AddServiceDiscovery(); // Configure health checks builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]) - .AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"]) .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) .AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]); @@ -91,7 +90,6 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => .WriteTo.Elasticsearch(es); }); builder.Services.AddOptions(); -builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.MongoDb)); builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); builder.Services.Configure(builder.Configuration.GetSection(Constants.ThirdParty.Privy)); builder.Services.AddControllers().AddJsonOptions(options => @@ -108,6 +106,27 @@ builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder => })); builder.Services.AddSignalR().AddJsonProtocol(); +// Add PostgreSQL DbContext for worker services +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions => + { + npgsqlOptions.CommandTimeout(60); + npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), + errorCodesToAdd: null); + }); + if (builder.Environment.IsDevelopment()) + { + options.EnableDetailedErrors(); + options.EnableSensitiveDataLogging(); + options.EnableThreadSafetyChecks(); + } + + options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + options.EnableServiceProviderCaching(); + options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning); +}, ServiceLifetime.Scoped); + builder.Services.RegisterWorkersDependencies(builder.Configuration); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(document => @@ -157,24 +176,6 @@ builder.WebHost.SetupDiscordBot(); var app = builder.Build(); app.UseSerilogRequestLogging(); -// Create MongoDB indexes on startup -try -{ - var indexService = app.Services.GetRequiredService(); - await indexService.CreateIndexesAsync(); -} -catch (Exception ex) -{ - // Log the error but don't fail the application startup - var logger = app.Services.GetRequiredService>(); - logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes."); -} - -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} - app.UseOpenApi(); app.UseSwaggerUI(c => { diff --git a/src/Managing.Api.Workers/appsettings.Development.json b/src/Managing.Api.Workers/appsettings.Development.json index b273bce..19023d0 100644 --- a/src/Managing.Api.Workers/appsettings.Development.json +++ b/src/Managing.Api.Workers/appsettings.Development.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://localhost:8086/", "Token": "" diff --git a/src/Managing.Api.Workers/appsettings.KaiServer.json b/src/Managing.Api.Workers/appsettings.KaiServer.json index 9640d76..2a451c1 100644 --- a/src/Managing.Api.Workers/appsettings.KaiServer.json +++ b/src/Managing.Api.Workers/appsettings.KaiServer.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "https://influx-db.apps.managing.live", "Organization": "managing-org", diff --git a/src/Managing.Api.Workers/appsettings.Oda-docker.json b/src/Managing.Api.Workers/appsettings.Oda-docker.json index bdfc6c9..5dee831 100644 --- a/src/Managing.Api.Workers/appsettings.Oda-docker.json +++ b/src/Managing.Api.Workers/appsettings.Oda-docker.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://managingdb:27017", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://influxdb:8086/", "Organization": "managing-org", diff --git a/src/Managing.Api.Workers/appsettings.Oda.json b/src/Managing.Api.Workers/appsettings.Oda.json index e758eb2..472643b 100644 --- a/src/Managing.Api.Workers/appsettings.Oda.json +++ b/src/Managing.Api.Workers/appsettings.Oda.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://localhost:8086/", "Organization": "managing-org", @@ -37,5 +33,6 @@ "WorkerTraderWatcher": false, "WorkerLeaderboard": false, "WorkerFundingRatesWatcher": false, - "WorkerGeneticAlgorithm": true + "WorkerGeneticAlgorithm": false, + "WorkerBundleBacktest": true } \ No newline at end of file diff --git a/src/Managing.Api.Workers/appsettings.Production.json b/src/Managing.Api.Workers/appsettings.Production.json index 7d24004..4f0fcb8 100644 --- a/src/Managing.Api.Workers/appsettings.Production.json +++ b/src/Managing.Api.Workers/appsettings.Production.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://root:gRerdhtg546FgrW@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "https://influx-db.apps.managing.live", "Organization": "managing-org", diff --git a/src/Managing.Api.Workers/appsettings.Sandbox.json b/src/Managing.Api.Workers/appsettings.Sandbox.json index a477322..10a649c 100644 --- a/src/Managing.Api.Workers/appsettings.Sandbox.json +++ b/src/Managing.Api.Workers/appsettings.Sandbox.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://srv-captain--influx-db:8086/", "Organization": "managing-org", diff --git a/src/Managing.Api.Workers/appsettings.SandboxLocal.json b/src/Managing.Api.Workers/appsettings.SandboxLocal.json index 8de4c89..ef99e1e 100644 --- a/src/Managing.Api.Workers/appsettings.SandboxLocal.json +++ b/src/Managing.Api.Workers/appsettings.SandboxLocal.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "https://influx-db.apps.managing.live", "Organization": "managing-org", diff --git a/src/Managing.Api.Workers/appsettings.json b/src/Managing.Api.Workers/appsettings.json index 170c050..e0cd8d5 100644 --- a/src/Managing.Api.Workers/appsettings.json +++ b/src/Managing.Api.Workers/appsettings.json @@ -1,13 +1,12 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://managingdb", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://influxdb:8086/", "Organization": "", "Token": "" }, + "PostgreSql": { + "ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres" + }, "Serilog": { "MinimumLevel": { "Default": "Information", @@ -17,6 +16,9 @@ } } }, + "N8n": { + "WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951" + }, "ElasticConfiguration": { "Uri": "http://elasticsearch:9200" }, diff --git a/src/Managing.Api/Controllers/AccountController.cs b/src/Managing.Api/Controllers/AccountController.cs index d5a553f..2099e0b 100644 --- a/src/Managing.Api/Controllers/AccountController.cs +++ b/src/Managing.Api/Controllers/AccountController.cs @@ -102,13 +102,13 @@ namespace Managing.Api.Controllers { var user = await GetUser(); var result = await _AccountService.SwapGmxTokensAsync( - user, - name, - request.FromTicker, - request.ToTicker, - request.Amount, - request.OrderType, - request.TriggerRatio, + user, + name, + request.FromTicker, + request.ToTicker, + request.Amount, + request.OrderType, + request.TriggerRatio, request.AllowedSlippage ); return Ok(result); @@ -126,11 +126,11 @@ namespace Managing.Api.Controllers { var user = await GetUser(); var result = await _AccountService.SendTokenAsync( - user, - name, - request.RecipientAddress, - request.Ticker, - request.Amount, + user, + name, + request.RecipientAddress, + request.Ticker, + request.Amount, request.ChainId ); return Ok(result); @@ -142,9 +142,9 @@ namespace Managing.Api.Controllers /// The name of the account to delete. /// An ActionResult indicating the outcome of the operation. [HttpDelete] - public ActionResult DeleteAccount(string name) + public async Task DeleteAccount(string name) { - var user = GetUser().Result; + var user = await GetUser(); return Ok(_AccountService.DeleteAccount(user, name)); } } diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 5a932d2..892e7b7 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -70,7 +70,8 @@ public class BacktestController : BaseController public async Task>> Backtests() { var user = await GetUser(); - return Ok(_backtester.GetBacktestsByUser(user)); + var backtests = await _backtester.GetBacktestsByUserAsync(user); + return Ok(backtests); } /// @@ -84,7 +85,7 @@ public class BacktestController : BaseController public async Task> Backtest(string id) { var user = await GetUser(); - var backtest = _backtester.GetBacktestByIdForUser(user, id); + var backtest = await _backtester.GetBacktestByIdForUserAsync(user, id); if (backtest == null) { @@ -103,7 +104,8 @@ public class BacktestController : BaseController public async Task DeleteBacktest(string id) { var user = await GetUser(); - return Ok(_backtester.DeleteBacktestByUser(user, id)); + var result = await _backtester.DeleteBacktestByUserAsync(user, id); + return Ok(result); } /// @@ -115,7 +117,7 @@ public class BacktestController : BaseController public async Task DeleteBacktests([FromBody] DeleteBacktestsRequest request) { var user = await GetUser(); - return Ok(_backtester.DeleteBacktestsByIdsForUser(user, request.BacktestIds)); + return Ok(await _backtester.DeleteBacktestsByIdsForUserAsync(user, request.BacktestIds)); } /// @@ -133,7 +135,7 @@ public class BacktestController : BaseController return BadRequest("Request ID is required"); } - var backtests = _backtester.GetBacktestsByRequestId(requestId); + var backtests = await _backtester.GetBacktestsByRequestIdAsync(requestId); return Ok(backtests); } @@ -177,7 +179,7 @@ public class BacktestController : BaseController } var (backtests, totalCount) = - _backtester.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder); + await _backtester.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy, sortOrder); var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); @@ -243,7 +245,7 @@ public class BacktestController : BaseController return BadRequest("Sort order must be 'asc' or 'desc'"); } - var (backtests, totalCount) = _backtester.GetBacktestsByUserPaginated(user, page, pageSize, sortBy, sortOrder); + var (backtests, totalCount) = await _backtester.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder); var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); var response = new PaginatedBacktestsResponse @@ -528,7 +530,7 @@ public class BacktestController : BaseController _backtester.DeleteBundleBacktestRequestByIdForUser(user, id); // Then, delete all related backtests - var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id); + var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id); return Ok(new { @@ -693,7 +695,7 @@ public class BacktestController : BaseController _geneticService.DeleteGeneticRequestByIdForUser(user, id); // Then, delete all related backtests - var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id); + var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id); return Ok(new { diff --git a/src/Managing.Api/Controllers/BaseController.cs b/src/Managing.Api/Controllers/BaseController.cs index 47891cb..d99ebb3 100644 --- a/src/Managing.Api/Controllers/BaseController.cs +++ b/src/Managing.Api/Controllers/BaseController.cs @@ -22,17 +22,16 @@ public abstract class BaseController : ControllerBase var identity = HttpContext?.User.Identity as ClaimsIdentity; if (identity != null) { - var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value; - var user = await _userService.GetUserByAddressAsync(address); - - if (user != null) + var address = identity.Claims.FirstOrDefault(c => c.Type == "address")?.Value; + if (address != null) + { + var user = await _userService.GetUserByAddressAsync(address); return user; + } throw new Exception("User not found for this token"); } throw new Exception("Not identity assigned to this token"); } - - } \ No newline at end of file diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index 506f55a..82395ad 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -128,8 +128,14 @@ public class BotController : BaseController var user = await GetUser(); + if (string.IsNullOrEmpty(user.AgentName)) + { + return BadRequest( + "Agent name is required to start a bot. Please configure your agent name in the user profile."); + } + // Get money management - either by name lookup or use provided object - MoneyManagement moneyManagement; + LightMoneyManagement moneyManagement; if (!string.IsNullOrEmpty(request.Config.MoneyManagementName)) { moneyManagement = @@ -144,12 +150,6 @@ public class BotController : BaseController moneyManagement = Map(request.Config.MoneyManagement); // Format percentage values if using custom money management moneyManagement?.FormatPercentage(); - - // Ensure user is set for custom money management - if (moneyManagement != null) - { - moneyManagement.User = user; - } } // Validate initialTradingBalance @@ -425,7 +425,7 @@ public class BotController : BaseController new StopBotCommand(bot.Identifier)); // Get the saved bot backup - var backup = _botService.GetBotBackup(bot.Identifier); + var backup = await _botService.GetBotBackup(bot.Identifier); if (backup != null) { _botService.StartBotFromBackup(backup); @@ -564,7 +564,8 @@ public class BotController : BaseController catch (Exception ex) { _logger.LogError(ex, "Error opening position manually"); - return StatusCode(500, $"Error opening position: {ex.Message}"); + return StatusCode(500, + $"Error opening position: {ex.Message}, {ex.InnerException?.Message} or {ex.StackTrace}"); } } @@ -699,20 +700,31 @@ public class BotController : BaseController } // Validate and get the money management - MoneyManagement moneyManagement = null; + LightMoneyManagement moneyManagement = null; if (!string.IsNullOrEmpty(request.MoneyManagementName)) { // Load money management by name - moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName); - if (moneyManagement == null) + var fullMoneyManagement = + await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName); + if (fullMoneyManagement == null) { return BadRequest($"Money management '{request.MoneyManagementName}' not found"); } - if (moneyManagement.User?.Name != user.Name) + if (fullMoneyManagement.User?.Name != user.Name) { return Forbid("You don't have permission to use this money management"); } + + // Convert to LightMoneyManagement + moneyManagement = new LightMoneyManagement + { + Name = fullMoneyManagement.Name, + Timeframe = fullMoneyManagement.Timeframe, + StopLoss = fullMoneyManagement.StopLoss, + TakeProfit = fullMoneyManagement.TakeProfit, + Leverage = fullMoneyManagement.Leverage + }; } else if (request.MoneyManagement != null) { @@ -720,9 +732,6 @@ public class BotController : BaseController moneyManagement = request.MoneyManagement; // Format percentage values if using custom money management moneyManagement.FormatPercentage(); - - // Ensure user is set for custom money management - moneyManagement.User = user; } else { diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 50fc803..f7b80db 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -206,14 +206,14 @@ public class DataController : ControllerBase /// A object containing spotlight data. [Authorize] [HttpGet("Spotlight")] - public ActionResult GetSpotlight() + public async Task> GetSpotlight() { - var overview = _cacheService.GetOrSave(nameof(SpotlightOverview), - () => { return _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); }, TimeSpan.FromMinutes(2)); - - if (overview?.Spotlights.Count < overview?.ScenarioCount || overview == null) + var cacheKey = $"Spotlight_{DateTime.Now.AddDays(-2).ToString("yyyy-MM-dd")}"; + var overview = _cacheService.GetValue(cacheKey); + if (overview == null) { - overview = _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); + overview = await _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); + _cacheService.SaveValue(cacheKey, overview, TimeSpan.FromMinutes(2)); } return Ok(overview); @@ -256,7 +256,7 @@ public class DataController : ControllerBase { // Map ScenarioRequest to domain Scenario object var domainScenario = MapScenarioRequestToScenario(request.Scenario); - indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles); + indicatorsValues = _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles); } return Ok(new CandlesWithIndicatorsResponse diff --git a/src/Managing.Api/Controllers/MoneyManagementController.cs b/src/Managing.Api/Controllers/MoneyManagementController.cs index c7e3329..8f4da96 100644 --- a/src/Managing.Api/Controllers/MoneyManagementController.cs +++ b/src/Managing.Api/Controllers/MoneyManagementController.cs @@ -40,8 +40,16 @@ public class MoneyManagementController : BaseController [HttpPost] public async Task> PostMoneyManagement(MoneyManagement moneyManagement) { - var user = await GetUser(); - return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement)); + try + { + var user = await GetUser(); + var result = await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement); + return Ok(result); + } + catch (Exception ex) + { + return StatusCode(500, $"Error creating/updating money management: {ex.Message}"); + } } /// @@ -52,8 +60,16 @@ public class MoneyManagementController : BaseController [Route("moneymanagements")] public async Task>> GetMoneyManagements() { - var user = await GetUser(); - return Ok(_moneyManagementService.GetMoneyMangements(user)); + try + { + var user = await GetUser(); + var moneyManagements = await _moneyManagementService.GetMoneyMangements(user); + return Ok(moneyManagements); + } + catch (Exception ex) + { + return StatusCode(500, $"Error retrieving money managements: {ex.Message}"); + } } /// @@ -64,8 +80,22 @@ public class MoneyManagementController : BaseController [HttpGet] public async Task> GetMoneyManagement(string name) { - var user = await GetUser(); - return Ok(await _moneyManagementService.GetMoneyMangement(user, name)); + try + { + var user = await GetUser(); + var result = await _moneyManagementService.GetMoneyMangement(user, name); + + if (result == null) + { + return NotFound($"Money management strategy '{name}' not found"); + } + + return Ok(result); + } + catch (Exception ex) + { + return StatusCode(500, $"Error retrieving money management: {ex.Message}"); + } } /// @@ -76,7 +106,21 @@ public class MoneyManagementController : BaseController [HttpDelete] public async Task DeleteMoneyManagement(string name) { - var user = await GetUser(); - return Ok(_moneyManagementService.DeleteMoneyManagement(user, name)); + try + { + var user = await GetUser(); + var result = await _moneyManagementService.DeleteMoneyManagement(user, name); + + if (!result) + { + return NotFound($"Money management strategy '{name}' not found or could not be deleted"); + } + + return Ok(new { success = true, message = $"Money management strategy '{name}' deleted successfully" }); + } + catch (Exception ex) + { + return StatusCode(500, $"Error deleting money management: {ex.Message}"); + } } } \ No newline at end of file diff --git a/src/Managing.Api/Controllers/ScenarioController.cs b/src/Managing.Api/Controllers/ScenarioController.cs index 5f00c1e..3db1642 100644 --- a/src/Managing.Api/Controllers/ScenarioController.cs +++ b/src/Managing.Api/Controllers/ScenarioController.cs @@ -43,7 +43,7 @@ public class ScenarioController : BaseController public async Task>> GetScenarios() { var user = await GetUser(); - var scenarios = _scenarioService.GetScenariosByUser(user); + var scenarios = await _scenarioService.GetScenariosByUserAsync(user); var scenarioViewModels = scenarios.Select(MapToScenarioViewModel); return Ok(scenarioViewModels); } @@ -59,7 +59,7 @@ public class ScenarioController : BaseController int? loopbackPeriod = null) { var user = await GetUser(); - var scenario = _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod); + var scenario = await _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod); var scenarioViewModel = MapToScenarioViewModel(scenario); return Ok(scenarioViewModel); } @@ -73,7 +73,7 @@ public class ScenarioController : BaseController public async Task DeleteScenario(string name) { var user = await GetUser(); - return Ok(_scenarioService.DeleteScenarioByUser(user, name)); + return Ok(await _scenarioService.DeleteScenarioByUser(user, name)); } // Update scenario @@ -81,7 +81,7 @@ public class ScenarioController : BaseController public async Task UpdateScenario(string name, List strategies, int? loopbackPeriod = null) { var user = await GetUser(); - return Ok(_scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod)); + return Ok(await _scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod)); } /// @@ -93,7 +93,7 @@ public class ScenarioController : BaseController public async Task>> GetIndicators() { var user = await GetUser(); - var indicators = _scenarioService.GetIndicatorsByUser(user); + var indicators = await _scenarioService.GetIndicatorsAsync(); var indicatorViewModels = indicators.Select(MapToIndicatorViewModel); return Ok(indicatorViewModels); } @@ -127,7 +127,7 @@ public class ScenarioController : BaseController int? cyclePeriods = null) { var user = await GetUser(); - var indicator = _scenarioService.CreateIndicatorForUser( + var indicator = await _scenarioService.CreateIndicatorForUser( user, indicatorType, name, @@ -153,7 +153,7 @@ public class ScenarioController : BaseController public async Task DeleteIndicator(string name) { var user = await GetUser(); - return Ok(_scenarioService.DeleteIndicatorByUser(user, name)); + return Ok(await _scenarioService.DeleteIndicatorByUser(user, name)); } // Update indicator @@ -172,7 +172,7 @@ public class ScenarioController : BaseController int? cyclePeriods = null) { var user = await GetUser(); - return Ok(_scenarioService.UpdateIndicatorByUser( + return Ok(await _scenarioService.UpdateIndicatorByUser( user, indicatorType, name, diff --git a/src/Managing.Api/Controllers/SettingsController.cs b/src/Managing.Api/Controllers/SettingsController.cs index bf755e2..b9199f6 100644 --- a/src/Managing.Api/Controllers/SettingsController.cs +++ b/src/Managing.Api/Controllers/SettingsController.cs @@ -1,19 +1,15 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Threading.Tasks; -using System.Collections.Generic; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Strategies; -using Managing.Domain.Scenarios; -using Managing.Domain.Users; -using static Managing.Common.Enums; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace Managing.Api.Controllers; +/// +/// 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. +/// [ApiController] [Authorize] [Route("[controller]")] @@ -22,28 +18,58 @@ public class SettingsController : BaseController { private readonly ISettingsService _settingsService; + /// + /// Initializes a new instance of the class. + /// + /// The service for managing application settings. + /// The service for user-related operations. public SettingsController(ISettingsService settingsService, IUserService userService) : base(userService) { _settingsService = settingsService; } + /// + /// Sets up initial application settings. + /// + /// A result indicating if the setup was successful. [HttpPost] - public ActionResult SetupSettings() + public async Task> 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}"); + } } + /// + /// Resets all application settings to their default values. + /// + /// A result indicating if the reset was successful. [HttpDelete] public async Task> 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}"); + } } /// /// Creates default configuration for backtesting including Money Management, Strategy, and Scenario + /// for the authenticated user. /// - /// A result indicating if the default configuration was created successfully + /// A result indicating if the default configuration was created successfully. [HttpPost] [Route("create-default-config")] public async Task> CreateDefaultConfiguration() @@ -52,9 +78,12 @@ public class SettingsController : BaseController { var user = await GetUser(); if (user == null) - return Unauthorized("User not found"); + { + return Unauthorized("User not found or authentication failed"); + } - return Ok(await _settingsService.CreateDefaultConfiguration(user)); + var result = await _settingsService.CreateDefaultConfiguration(user); + return Ok(result); } catch (Exception ex) { diff --git a/src/Managing.Api/Controllers/TradingController.cs b/src/Managing.Api/Controllers/TradingController.cs index 891b9c1..04ab52d 100644 --- a/src/Managing.Api/Controllers/TradingController.cs +++ b/src/Managing.Api/Controllers/TradingController.cs @@ -51,17 +51,6 @@ public class TradingController : BaseController _moneyManagementService = moneyManagementService; } - /// - /// Retrieves a list of positions based on the initiator type. - /// - /// The initiator of the position (e.g., User, System). - /// A list of positions. - [HttpGet("GetPositions")] - public async Task>> GetPositions(PositionInitiator positionInitiator) - { - var result = await _mediator.Send(new GetPositionsCommand(positionInitiator)); - return Ok(result); - } /// /// Retrieves a specific trade by account name, ticker, and exchange order ID. @@ -98,7 +87,7 @@ public class TradingController : BaseController [HttpPost("ClosePosition")] public async Task> ClosePosition(string identifier) { - var position = _tradingService.GetPositionByIdentifier(identifier); + var position = await _tradingService.GetPositionByIdentifierAsync(identifier); var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position)); return Ok(result); } diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj index 2f5b7ed..14e28c0 100644 --- a/src/Managing.Api/Managing.Api.csproj +++ b/src/Managing.Api/Managing.Api.csproj @@ -11,7 +11,7 @@ - + @@ -52,8 +52,4 @@ Always - - - - diff --git a/src/Managing.Api/Models/Responses/TradingBotResponse.cs b/src/Managing.Api/Models/Responses/TradingBotResponse.cs index 73a1956..ce0989e 100644 --- a/src/Managing.Api/Models/Responses/TradingBotResponse.cs +++ b/src/Managing.Api/Models/Responses/TradingBotResponse.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using Managing.Domain.Bots; using Managing.Domain.Candles; -using Managing.Domain.Strategies; using Managing.Domain.Trades; namespace Managing.Api.Models.Responses @@ -11,56 +10,67 @@ namespace Managing.Api.Models.Responses /// /// Current status of the bot (Up, Down, etc.) /// - [Required] public string Status { get; internal set; } - + [Required] + public string Status { get; internal set; } + /// /// List of signals generated by the bot /// - [Required] public List Signals { get; internal set; } - + [Required] + public List Signals { get; internal set; } + /// /// List of positions opened by the bot /// - [Required] public List Positions { get; internal set; } - + [Required] + public List Positions { get; internal set; } + /// /// Candles used by the bot for analysis /// - [Required] public List Candles { get; internal set; } - + [Required] + public List Candles { get; internal set; } + /// /// Current win rate percentage /// - [Required] public int WinRate { get; internal set; } - + [Required] + public int WinRate { get; internal set; } + /// /// Current profit and loss /// - [Required] public decimal ProfitAndLoss { get; internal set; } - + [Required] + public decimal ProfitAndLoss { get; internal set; } + /// /// Unique identifier for the bot /// - [Required] public string Identifier { get; set; } - + [Required] + public string Identifier { get; set; } + /// /// Agent name associated with the bot /// - [Required] public string AgentName { get; set; } - + [Required] + public string AgentName { get; set; } + /// /// The full trading bot configuration /// - [Required] public TradingBotConfig Config { get; internal set; } - + [Required] + public TradingBotConfig Config { get; internal set; } + /// /// The time when the bot was created /// - [Required] public DateTime CreateDate { get; internal set; } - + [Required] + public DateTime CreateDate { get; internal set; } + /// /// The time when the bot was started /// - [Required] public DateTime StartupTime { get; internal set; } + [Required] + public DateTime StartupTime { get; internal set; } } } \ No newline at end of file diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index 6fb4f31..1e458b6 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -10,11 +10,12 @@ using Managing.Bootstrap; using Managing.Common; using Managing.Core.Middleawares; using Managing.Infrastructure.Databases.InfluxDb.Models; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Configurations; +using Managing.Infrastructure.Databases.PostgreSql; +using Managing.Infrastructure.Databases.PostgreSql.Configurations; using Managing.Infrastructure.Evm.Models.Privy; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; @@ -71,7 +72,7 @@ builder.Services.AddServiceDiscovery(); builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]); -var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"]; +var postgreSqlConnectionString = builder.Configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"]; var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; @@ -87,9 +88,38 @@ builder.Services.AddHttpClient("GmxHealthCheck") builder.Services.AddSingleton(sp => new Web3ProxyHealthCheck(sp.GetRequiredService(), web3ProxyUrl)); +// Add PostgreSQL DbContext with improved concurrency and connection management +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions => + { + // Configure connection pooling and timeout settings for better concurrency + npgsqlOptions.CommandTimeout(60); // Increase command timeout for complex queries + npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), + errorCodesToAdd: null); + }); + + // Enable detailed errors in development + if (builder.Environment.IsDevelopment()) + { + options.EnableDetailedErrors(); + options.EnableSensitiveDataLogging(); + options.EnableThreadSafetyChecks(); // Enable thread safety checks in development + } + + // Configure query tracking behavior for better performance + options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Default to no tracking for better performance + + // Enable service provider caching for better performance + options.EnableServiceProviderCaching(); + + // Enable connection resiliency for backtest and high-load scenarios + options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning); // Log warnings for connection issues +}, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation + // Add specific health checks for databases and other services builder.Services.AddHealthChecks() - .AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"]) + .AddNpgSql(postgreSqlConnectionString, name: "postgresql", tags: ["database"]) .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) .AddCheck("web3proxy", tags: ["api", "external"]) .AddCheck("candle-data", tags: ["database", "candles"]) @@ -120,7 +150,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => }); builder.Services.AddOptions(); -builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.MongoDb)); +builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.PostgreSql)); builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); builder.Services.Configure(builder.Configuration.GetSection(Constants.ThirdParty.Privy)); builder.Services.AddControllers().AddJsonOptions(options => @@ -209,25 +239,6 @@ if (builder.Configuration.GetValue("EnableBotManager", false)) // App var app = builder.Build(); app.UseSerilogRequestLogging(); - -// Create MongoDB indexes on startup -try -{ - var indexService = app.Services.GetRequiredService(); - await indexService.CreateIndexesAsync(); -} -catch (Exception ex) -{ - // Log the error but don't fail the application startup - var logger = app.Services.GetRequiredService>(); - logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes."); -} - -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} - app.UseOpenApi(); app.UseSwaggerUI(c => { diff --git a/src/Managing.Api/appsettings.Development.json b/src/Managing.Api/appsettings.Development.json deleted file mode 100644 index 2433dc4..0000000 --- a/src/Managing.Api/appsettings.Development.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Oda-Sandbox.json b/src/Managing.Api/appsettings.Oda-Sandbox.json index 92df16f..47903ff 100644 --- a/src/Managing.Api/appsettings.Oda-Sandbox.json +++ b/src/Managing.Api/appsettings.Oda-Sandbox.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://managingdb:27017", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://influxdb:8086/", "Organization": "", diff --git a/src/Managing.Api/appsettings.Oda-docker.json b/src/Managing.Api/appsettings.Oda-docker.json index 7592b39..bb705d9 100644 --- a/src/Managing.Api/appsettings.Oda-docker.json +++ b/src/Managing.Api/appsettings.Oda-docker.json @@ -1,10 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://managingdb:27017", - "DatabaseName": "ManagingDb", - "UserName": "admin", - "Password": "!MotdepasseFort11" - }, "InfluxDb": { "Url": "http://influxdb:8086/", "Organization": "managing-org", diff --git a/src/Managing.Api/appsettings.Oda.json b/src/Managing.Api/appsettings.Oda.json index 2baef6d..b7f3cee 100644 --- a/src/Managing.Api/appsettings.Oda.json +++ b/src/Managing.Api/appsettings.Oda.json @@ -1,13 +1,12 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "http://localhost:8086/", "Organization": "managing-org", "Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==" }, + "PostgreSql": { + "ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres" + }, "Privy": { "AppId": "cm6f47n1l003jx7mjwaembhup", "AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF" @@ -39,5 +38,6 @@ }, "AllowedHosts": "*", "WorkerBotManager": true, - "WorkerBalancesTracking": true + "WorkerBalancesTracking": false, + "WorkerNotifyBundleBacktest": true } \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Production.json b/src/Managing.Api/appsettings.Production.json index a2384f3..7419fd0 100644 --- a/src/Managing.Api/appsettings.Production.json +++ b/src/Managing.Api/appsettings.Production.json @@ -1,7 +1,6 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:vgRehYTdhghDR@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" + "PostgreSql": { + "ConnectionString": "Host=apps.prod.live;Port=5432;Database=managing;Username=postgres;Password=postgres" }, "InfluxDb": { "Url": "https://influx-db.apps.managing.live", diff --git a/src/Managing.Api/appsettings.Sandbox.json b/src/Managing.Api/appsettings.Sandbox.json index 891728f..d0484ba 100644 --- a/src/Managing.Api/appsettings.Sandbox.json +++ b/src/Managing.Api/appsettings.Sandbox.json @@ -1,7 +1,6 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" + "PostgreSql": { + "ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37" }, "InfluxDb": { "Url": "http://srv-captain--influx-db:8086/", diff --git a/src/Managing.Api/appsettings.SandboxLocal.json b/src/Managing.Api/appsettings.SandboxLocal.json index 7addd6e..fabed13 100644 --- a/src/Managing.Api/appsettings.SandboxLocal.json +++ b/src/Managing.Api/appsettings.SandboxLocal.json @@ -1,8 +1,4 @@ { - "ManagingDatabase": { - "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256", - "DatabaseName": "ManagingDb" - }, "InfluxDb": { "Url": "https://influx-db.apps.managing.live", "Organization": "managing-org", @@ -12,6 +8,9 @@ "AppId": "cm6f47n1l003jx7mjwaembhup", "AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF" }, + "PostgreSql": { + "ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37" + }, "Serilog": { "MinimumLevel": { "Default": "Information", diff --git a/src/Managing.Api/appsettings.json b/src/Managing.Api/appsettings.json index 79e4cfc..eb37ef0 100644 --- a/src/Managing.Api/appsettings.json +++ b/src/Managing.Api/appsettings.json @@ -12,9 +12,8 @@ "Jwt": { "Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6" }, - "ManagingDatabase": { - "ConnectionString": "mongodb://managingdb", - "DatabaseName": "ManagingDb" + "PostgreSql": { + "ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres" }, "InfluxDb": { "Url": "http://influxdb:8086/", diff --git a/src/Managing.Application/Abstractions/IMoneyManagementService.cs b/src/Managing.Application.Abstractions/IMoneyManagementService.cs similarity index 60% rename from src/Managing.Application/Abstractions/IMoneyManagementService.cs rename to src/Managing.Application.Abstractions/IMoneyManagementService.cs index 15c3ed6..5184fe5 100644 --- a/src/Managing.Application/Abstractions/IMoneyManagementService.cs +++ b/src/Managing.Application.Abstractions/IMoneyManagementService.cs @@ -1,4 +1,4 @@ -using Managing.Domain.MoneyManagements; +using Managing.Domain.MoneyManagements; using Managing.Domain.Users; namespace Managing.Application.Abstractions @@ -8,8 +8,8 @@ namespace Managing.Application.Abstractions Task CreateOrUpdateMoneyManagement(User user, MoneyManagement request); Task GetMoneyMangement(User user, string name); Task GetMoneyMangement(string name); - IEnumerable GetMoneyMangements(User user); - bool DeleteMoneyManagement(User user, string name); - bool DeleteMoneyManagements(User user); + Task> GetMoneyMangements(User user); + Task DeleteMoneyManagement(User user, string name); + Task DeleteMoneyManagements(User user); } -} +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/IScenarioService.cs b/src/Managing.Application.Abstractions/IScenarioService.cs new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/Managing.Application.Abstractions/IScenarioService.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs index 7fa7423..f635bd6 100644 --- a/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs @@ -8,5 +8,5 @@ public interface IAccountRepository Task GetAccountByKeyAsync(string key); Task InsertAccountAsync(Account account); void DeleteAccountByName(string name); - IEnumerable GetAccounts(); + Task> GetAccountsAsync(); } diff --git a/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs index d0adaf6..b9b208e 100644 --- a/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs @@ -7,25 +7,39 @@ public interface IBacktestRepository { void InsertBacktestForUser(User user, Backtest result); IEnumerable GetBacktestsByUser(User user); + Task> GetBacktestsByUserAsync(User user); IEnumerable GetBacktestsByRequestId(string requestId); + Task> GetBacktestsByRequestIdAsync(string requestId); (IEnumerable Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); + Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, + int page, + int pageSize, string sortBy = "score", string sortOrder = "desc"); + (IEnumerable Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); - Backtest GetBacktestByIdForUser(User user, string id); - void DeleteBacktestByIdForUser(User user, string id); - void DeleteBacktestsByIdsForUser(User user, IEnumerable ids); + Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, + int pageSize, string sortBy = "score", string sortOrder = "desc"); + + Task GetBacktestByIdForUserAsync(User user, string id); + Task DeleteBacktestByIdForUserAsync(User user, string id); + Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable ids); void DeleteAllBacktestsForUser(User user); - void DeleteBacktestsByRequestId(string requestId); + Task DeleteBacktestsByRequestIdAsync(string requestId); // Bundle backtest methods void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest); IEnumerable GetBundleBacktestRequestsByUser(User user); + Task> GetBundleBacktestRequestsByUserAsync(User user); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); + Task GetBundleBacktestRequestByIdForUserAsync(User user, string id); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); + Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest); void DeleteBundleBacktestRequestByIdForUser(User user, string id); + Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id); IEnumerable GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status); + Task> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs index b47a4c0..9459e6d 100644 --- a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs @@ -5,7 +5,8 @@ namespace Managing.Application.Abstractions.Repositories; public interface IBotRepository { Task InsertBotAsync(BotBackup bot); - IEnumerable GetBots(); + Task> GetBotsAsync(); Task UpdateBackupBot(BotBackup bot); Task DeleteBotBackup(string botName); + Task GetBotByIdentifierAsync(string identifier); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IGeneticRepository.cs b/src/Managing.Application.Abstractions/Repositories/IGeneticRepository.cs index aed8ac9..ce31128 100644 --- a/src/Managing.Application.Abstractions/Repositories/IGeneticRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IGeneticRepository.cs @@ -34,7 +34,7 @@ public interface IGeneticRepository /// Updates a genetic request /// /// The genetic request to update - void UpdateGeneticRequest(GeneticRequest geneticRequest); + Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest); /// /// Deletes a genetic request by ID for a user @@ -50,8 +50,9 @@ public interface IGeneticRepository void DeleteAllGeneticRequestsForUser(User user); /// - /// Gets all pending genetic requests across all users + /// Gets all genetic requests by status across all users /// - /// Collection of pending genetic requests - IEnumerable GetPendingGeneticRequests(); + /// The status to filter by + /// Collection of genetic requests + Task> GetGeneticRequestsAsync(GeneticRequestStatus status); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs b/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs index 7eb38a6..3cb6df4 100644 --- a/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs @@ -6,15 +6,15 @@ namespace Managing.Application.Abstractions.Repositories; public interface ISettingsRepository { Task GetMoneyManagement(string name); - Task InsertMoneyManagement(MoneyManagement request); - void UpdateMoneyManagement(MoneyManagement moneyManagement); - IEnumerable GetMoneyManagements(); - void DeleteMoneyManagement(string name); - void DeleteMoneyManagements(); - + Task InsertMoneyManagement(LightMoneyManagement request, User user); + Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user); + Task> GetMoneyManagementsAsync(); + Task DeleteMoneyManagementAsync(string name); + Task DeleteMoneyManagementsAsync(); + // User-specific operations (these should eventually replace the above methods) Task GetMoneyManagementByUser(User user, string name); - IEnumerable GetMoneyManagementsByUser(User user); - void DeleteMoneyManagementByUser(User user, string name); - void DeleteMoneyManagementsByUser(User user); -} + Task> GetMoneyManagementsByUserAsync(User user); + Task DeleteMoneyManagementByUserAsync(User user, string name); + Task DeleteMoneyManagementsByUserAsync(User user); +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs b/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs index da79729..748e52f 100644 --- a/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs @@ -6,19 +6,26 @@ public interface IStatisticRepository { Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker); IList GetTopVolumeTickers(DateTime date); + Task> GetTopVolumeTickersAsync(DateTime date); + Task SaveSpotligthtOverview(SpotlightOverview overview); IList GetSpotlightOverviews(DateTime date); + Task> GetSpotlightOverviewsAsync(DateTime date); void UpdateSpotlightOverview(SpotlightOverview overview); - List GetBestTraders(); - void UpdateBestTrader(Trader trader); + Task UpdateSpotlightOverviewAsync(SpotlightOverview overview); + + Task> GetBestTradersAsync(); + Task UpdateBestTraderAsync(Trader trader); Task InsertBestTrader(Trader trader); Task RemoveBestTrader(Trader trader); - List GetBadTraders(); - void UpdateBadTrader(Trader trader); + + Task> GetBadTradersAsync(); + Task UpdateBadTraderAsync(Trader trader); Task InsertBadTrader(Trader trader); Task RemoveBadTrader(Trader trader); - List GetFundingRates(); + + Task> GetFundingRatesAsync(); Task RemoveFundingRate(FundingRate oldRate); Task InsertFundingRate(FundingRate newRate); - void UpdateFundingRate(FundingRate oldRate, FundingRate newRate); + Task UpdateFundingRateAsync(FundingRate oldRate, FundingRate newRate); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs index 9c8120c..011c0b7 100644 --- a/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs @@ -8,27 +8,23 @@ namespace Managing.Application.Abstractions.Repositories; public interface ITradingRepository { - Scenario GetScenarioByName(string scenario); - void InsertSignal(Signal signal); - IEnumerable GetSignalsByUser(User user); - Signal GetSignalByIdentifier(string identifier, User user = null); - void InsertPosition(Position position); - void UpdatePosition(Position position); - Indicator GetStrategyByName(string strategy); - void InsertScenario(Scenario scenario); - void InsertStrategy(Indicator indicator); - IEnumerable GetScenarios(); - IEnumerable GetIndicators(); - void DeleteScenario(string name); - void DeleteIndicator(string name); - void DeleteScenarios(); - void DeleteIndicators(); - Position GetPositionByIdentifier(string identifier); - IEnumerable GetPositions(PositionInitiator positionInitiator); - IEnumerable GetPositionsByStatus(PositionStatus positionStatus); - Fee GetFee(TradingExchanges exchange); - void InsertFee(Fee fee); - void UpdateFee(Fee fee); - void UpdateScenario(Scenario scenario); - void UpdateStrategy(Indicator indicator); + Task GetScenarioByNameAsync(string scenario); + Task> GetSignalsByUserAsync(User user); + Task GetSignalByIdentifierAsync(string identifier, User user = null); + Task InsertPositionAsync(Position position); + Task UpdatePositionAsync(Position position); + Task GetStrategyByNameAsync(string strategy); + Task InsertScenarioAsync(Scenario scenario); + Task InsertStrategyAsync(Indicator indicator); + Task> GetScenariosAsync(); + Task> GetStrategiesAsync(); + Task> GetIndicatorsAsync(); + Task DeleteScenarioAsync(string name); + Task DeleteIndicatorAsync(string name); + Task GetPositionByIdentifierAsync(string identifier); + Task> GetPositionsAsync(PositionInitiator positionInitiator); + Task> GetPositionsByStatusAsync(PositionStatus positionStatus); + + Task UpdateScenarioAsync(Scenario scenario); + Task UpdateStrategyAsync(Indicator indicator); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs b/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs index 6f11a7b..420c77d 100644 --- a/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs @@ -8,7 +8,7 @@ public interface IWorkerRepository Task DisableWorker(Enums.WorkerType workerType); Task EnableWorker(Enums.WorkerType workerType); Task GetWorkerAsync(Enums.WorkerType workerType); - IEnumerable GetWorkers(); + Task> GetWorkers(); Task InsertWorker(Worker worker); Task UpdateWorker(Enums.WorkerType workerType, int executionCount); } diff --git a/src/Managing.Application.Abstractions/Services/IAccountService.cs b/src/Managing.Application.Abstractions/Services/IAccountService.cs index 39f84a3..4d42ebf 100644 --- a/src/Managing.Application.Abstractions/Services/IAccountService.cs +++ b/src/Managing.Application.Abstractions/Services/IAccountService.cs @@ -9,15 +9,29 @@ public interface IAccountService Task CreateAccount(User user, Account account); bool DeleteAccount(User user, string name); IEnumerable GetAccountsByUser(User user, bool hideSecrets = true); - IEnumerable GetAccounts(bool hideSecrets, bool getBalance); + Task> GetAccountsByUserAsync(User user, bool hideSecrets = true); + Task> GetAccounts(bool hideSecrets, bool getBalance); + Task> GetAccountsAsync(bool hideSecrets, bool getBalance); Task GetAccount(string name, bool hideSecrets, bool getBalance); - Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance); - Task GetAccountByKey(string key, bool hideSecrets, bool getBalance); + public Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance); + public Task GetAccountByKey(string key, bool hideSecrets, bool getBalance); + + /// + /// Gets an account by name directly from the repository. + /// + /// The name of the account to find + /// Whether to hide sensitive information + /// Whether to fetch the current balance + /// The found account or null if not found + Task GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false); + IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets = true); + Task> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true); Task GetGmxClaimableSummaryAsync(User user, string accountName); Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5); - Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null); + Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, + decimal amount, int? chainId = null); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs index 534b33e..f22c0ef 100644 --- a/src/Managing.Application.Abstractions/Services/IBacktester.cs +++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs @@ -50,25 +50,34 @@ namespace Managing.Application.Abstractions.Services object metadata = null); // Additional methods for backtest management - bool DeleteBacktest(string id); + Task DeleteBacktestAsync(string id); bool DeleteBacktests(); IEnumerable GetBacktestsByUser(User user); + Task> GetBacktestsByUserAsync(User user); IEnumerable GetBacktestsByRequestId(string requestId); + Task> GetBacktestsByRequestIdAsync(string requestId); (IEnumerable Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); - Backtest GetBacktestByIdForUser(User user, string id); - bool DeleteBacktestByUser(User user, string id); - bool DeleteBacktestsByIdsForUser(User user, IEnumerable ids); + Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); + Task GetBacktestByIdForUserAsync(User user, string id); + Task DeleteBacktestByUserAsync(User user, string id); + Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable ids); bool DeleteBacktestsByUser(User user); - bool DeleteBacktestsByRequestId(string requestId); + Task DeleteBacktestsByRequestIdAsync(string requestId); (IEnumerable Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); + Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); // Bundle backtest methods void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest); IEnumerable GetBundleBacktestRequestsByUser(User user); + Task> GetBundleBacktestRequestsByUserAsync(User user); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); + Task GetBundleBacktestRequestByIdForUserAsync(User user, string id); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); + Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest); void DeleteBundleBacktestRequestByIdForUser(User user, string id); + Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id); IEnumerable GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status); + Task> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status); } diff --git a/src/Managing.Application.Abstractions/Services/IExchangeService.cs b/src/Managing.Application.Abstractions/Services/IExchangeService.cs index b5f6861..43fd508 100644 --- a/src/Managing.Application.Abstractions/Services/IExchangeService.cs +++ b/src/Managing.Application.Abstractions/Services/IExchangeService.cs @@ -25,7 +25,7 @@ public interface IExchangeService Task GetBalance(Account account, bool isForPaperTrading = false); Task> GetBalances(Account account, bool isForPaperTrading = false); - decimal GetPrice(Account account, Ticker ticker, DateTime date); + Task GetPrice(Account account, Ticker ticker, DateTime date); Task GetTrade(Account account, string order, Ticker ticker); Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval, @@ -54,7 +54,7 @@ public interface IExchangeService Task> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe, DateTime endDate); - decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction); + Task GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction); Orderbook GetOrderbook(Account account, Ticker ticker); Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, diff --git a/src/Managing.Application.Abstractions/Services/IGeneticService.cs b/src/Managing.Application.Abstractions/Services/IGeneticService.cs index 6b6950d..8c6e996 100644 --- a/src/Managing.Application.Abstractions/Services/IGeneticService.cs +++ b/src/Managing.Application.Abstractions/Services/IGeneticService.cs @@ -64,7 +64,7 @@ public interface IGeneticService /// Updates a genetic request /// /// The genetic request to update - void UpdateGeneticRequest(GeneticRequest geneticRequest); + Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest); /// /// Deletes a genetic request by ID for a user @@ -74,10 +74,11 @@ public interface IGeneticService void DeleteGeneticRequestByIdForUser(User user, string id); /// - /// Gets all pending genetic requests across all users + /// Gets all genetic requests by status across all users /// - /// Collection of pending genetic requests - IEnumerable GetPendingGeneticRequests(); + /// The status to filter by + /// Collection of genetic requests + Task> GetGeneticRequestsAsync(GeneticRequestStatus status); /// /// Runs the genetic algorithm for a specific request diff --git a/src/Managing.Application.Abstractions/Services/IStatisticService.cs b/src/Managing.Application.Abstractions/Services/IStatisticService.cs index 5baf556..5b615a1 100644 --- a/src/Managing.Application.Abstractions/Services/IStatisticService.cs +++ b/src/Managing.Application.Abstractions/Services/IStatisticService.cs @@ -13,9 +13,13 @@ public interface IStatisticService int pageSize = 10); List GetBadTraders(); + Task> GetBadTradersAsync(); List GetBestTraders(); - SpotlightOverview GetLastSpotlight(DateTime dateTime); + Task> GetBestTradersAsync(); + Task GetLastSpotlight(DateTime dateTime); + Task GetLastSpotlightAsync(DateTime dateTime); IList GetLastTopVolumeTicker(); + Task> GetLastTopVolumeTickerAsync(); Task> GetLeadboardPositons(); Task> GetTickers(); Task UpdateLeaderboard(); diff --git a/src/Managing.Application.Abstractions/Services/ISynthPredictionService.cs b/src/Managing.Application.Abstractions/Services/ISynthPredictionService.cs index 4971d7d..6e0b096 100644 --- a/src/Managing.Application.Abstractions/Services/ISynthPredictionService.cs +++ b/src/Managing.Application.Abstractions/Services/ISynthPredictionService.cs @@ -1,6 +1,4 @@ using Managing.Domain.Bots; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Strategies; using Managing.Domain.Synth.Models; using static Managing.Common.Enums; @@ -70,7 +68,7 @@ public interface ISynthPredictionService /// Whether this is a backtest /// Custom probability thresholds for decision-making. If null, uses default thresholds. /// Comprehensive signal validation result including confidence, probabilities, and risk analysis - Task ValidateSignalAsync(Signal signal, decimal currentPrice, + Task ValidateSignalAsync(LightSignal signal, decimal currentPrice, TradingBotConfig botConfig, bool isBacktest, Dictionary customThresholds = null); /// @@ -105,5 +103,5 @@ public interface ISynthPredictionService /// Position direction /// Money management settings /// Estimated liquidation price - decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, MoneyManagement moneyManagement); + decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, LightMoneyManagement moneyManagement); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/ITradingService.cs b/src/Managing.Application.Abstractions/Services/ITradingService.cs index 2fbc414..2d0d846 100644 --- a/src/Managing.Application.Abstractions/Services/ITradingService.cs +++ b/src/Managing.Application.Abstractions/Services/ITradingService.cs @@ -14,36 +14,28 @@ namespace Managing.Application.Abstractions.Services; public interface ITradingService { - Scenario GetScenarioByName(string scenario); - void InsertSignal(Signal signal); - void InsertPosition(Position position); - void UpdatePosition(Position position); - Indicator GetStrategyByName(string strategy); - void InsertScenario(Scenario scenario); - void InsertStrategy(Indicator indicator); - IEnumerable GetScenarios(); - IEnumerable GetStrategies(); - void DeleteScenario(string name); - void DeleteStrategy(string name); - void DeleteScenarios(); - void DeleteStrategies(); - Position GetPositionByIdentifier(string identifier); - IEnumerable GetPositions(PositionInitiator positionInitiator); - IEnumerable GetPositions(); - IEnumerable GetPositionsByStatus(PositionStatus positionStatus); + Task GetScenarioByNameAsync(string scenario); + Task InsertPositionAsync(Position position); + Task UpdatePositionAsync(Position position); + Task GetStrategyByNameAsync(string strategy); + Task InsertScenarioAsync(Scenario scenario); + Task InsertStrategyAsync(Indicator indicator); + Task> GetScenariosAsync(); + Task> GetStrategiesAsync(); + Task DeleteScenarioAsync(string name); + Task DeleteStrategyAsync(string name); + Task GetPositionByIdentifierAsync(string identifier); Task ManagePosition(Account account, Position position); - void UpdateFee(TradingExchanges evm); - decimal GetFee(Account account, bool isForPaperTrading = false); + Task WatchTrader(); - IEnumerable GetTradersWatch(); - void UpdateDeltaNeutralOpportunities(); - void UpdateScenario(Scenario scenario); - void UpdateStrategy(Indicator indicator); + Task> GetTradersWatch(); + Task UpdateScenarioAsync(Scenario scenario); + Task UpdateStrategyAsync(Indicator indicator); Task> GetBrokerPositions(Account account); Task InitPrivyWallet(string publicAddress); // Synth API integration methods - Task ValidateSynthSignalAsync(Signal signal, decimal currentPrice, + Task ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice, TradingBotConfig botConfig, bool isBacktest); @@ -59,7 +51,7 @@ public interface ITradingService /// The scenario containing indicators. /// The candles to calculate indicators for. /// A dictionary of indicator types to their calculated values. - Task> CalculateIndicatorsValuesAsync( - Scenario scenario, + Dictionary CalculateIndicatorsValuesAsync( + Scenario scenario, List candles); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IUserService.cs b/src/Managing.Application.Abstractions/Services/IUserService.cs index 258534a..a056352 100644 --- a/src/Managing.Application.Abstractions/Services/IUserService.cs +++ b/src/Managing.Application.Abstractions/Services/IUserService.cs @@ -9,5 +9,5 @@ public interface IUserService Task UpdateAgentName(User user, string agentName); Task UpdateAvatarUrl(User user, string avatarUrl); Task UpdateTelegramChannel(User user, string telegramChannel); - User GetUser(string name); -} + Task GetUser(string name); +} \ No newline at end of file diff --git a/src/Managing.Application.Tests/BotsTests.cs b/src/Managing.Application.Tests/BotsTests.cs index e16521b..0b44264 100644 --- a/src/Managing.Application.Tests/BotsTests.cs +++ b/src/Managing.Application.Tests/BotsTests.cs @@ -5,12 +5,15 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Backtesting; using Managing.Application.Bots.Base; +using Managing.Application.Hubs; +using Managing.Application.ManageBot; using Managing.Core; using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; +using Microsoft.AspNetCore.SignalR; using Moq; using Newtonsoft.Json; using Xunit; @@ -35,6 +38,8 @@ namespace Managing.Application.Tests var scenarioService = new Mock().Object; var messengerService = new Mock().Object; var kaigenService = new Mock().Object; + var backupBotService = new Mock().Object; + var hubContext = new Mock>().Object; var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); var botService = new Mock().Object; @@ -44,9 +49,9 @@ namespace Managing.Application.Tests discordService, _accountService.Object, _tradingService.Object, - botService); + botService, backupBotService); _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger, - scenarioService, _accountService.Object, messengerService, kaigenService); + scenarioService, _accountService.Object, messengerService, kaigenService, hubContext); _elapsedTimes = new List(); // Initialize cross-platform file paths diff --git a/src/Managing.Application.Tests/IndicatorTests.cs b/src/Managing.Application.Tests/IndicatorTests.cs index ffa838f..a1c48db 100644 --- a/src/Managing.Application.Tests/IndicatorTests.cs +++ b/src/Managing.Application.Tests/IndicatorTests.cs @@ -1,6 +1,5 @@ using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; -using Managing.Domain.Strategies; using Managing.Domain.Strategies.Signals; using Managing.Domain.Strategies.Trends; using Xunit; @@ -26,7 +25,7 @@ namespace Managing.Application.Tests // Arrange var rsiStrategy = new RsiDivergenceIndicator("unittest", 5); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -39,7 +38,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(rsiStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } @@ -60,7 +59,7 @@ namespace Managing.Application.Tests var account = GetAccount(exchange); var rsiStrategy = new RsiDivergenceIndicator("unittest", 5); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -73,7 +72,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(rsiStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); } @@ -87,7 +86,7 @@ namespace Managing.Application.Tests var account = GetAccount(exchange); var rsiStrategy = new MacdCrossIndicator("unittest", 12, 26, 9); var candles = await _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe); - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -100,7 +99,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(rsiStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } @@ -114,7 +113,7 @@ namespace Managing.Application.Tests var account = GetAccount(exchange); var superTrendStrategy = new SuperTrendIndicator("unittest", 10, 3); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -127,7 +126,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(superTrendStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } @@ -142,7 +141,7 @@ namespace Managing.Application.Tests var chandelierExitStrategy = new ChandelierExitIndicator("unittest", 22, 3); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe, false) .Result; - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -155,7 +154,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(chandelierExitStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } @@ -169,7 +168,7 @@ namespace Managing.Application.Tests var account = GetAccount(exchange); var emaTrendSrategy = new EmaTrendIndicator("unittest", 200); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; - var resultSignal = new List(); + var resultSignal = new List(); // Act foreach (var candle in candles) @@ -182,7 +181,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(emaTrendSrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } @@ -197,7 +196,7 @@ namespace Managing.Application.Tests var account = GetAccount(exchange); var stochRsiStrategy = new StochRsiTrendIndicator("unittest", 14, 14, 3, 1); var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; - var resultSignal = new List(); + var resultSignal = new List(); // var json = JsonConvert.SerializeObject(candles); // File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-candles.json", json); @@ -214,7 +213,7 @@ namespace Managing.Application.Tests resultSignal.AddRange(stochRsiStrategy.Signals); // Assert - Assert.IsType>(resultSignal); + Assert.IsType>(resultSignal); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); } diff --git a/src/Managing.Application.Tests/PositionTests.cs b/src/Managing.Application.Tests/PositionTests.cs index dffb9cb..1fa3c2d 100644 --- a/src/Managing.Application.Tests/PositionTests.cs +++ b/src/Managing.Application.Tests/PositionTests.cs @@ -51,7 +51,8 @@ public class PositionTests : BaseTests Open = openTrade }; var command = new ClosePositionCommand(position); - _ = _tradingService.Setup(m => m.GetPositionByIdentifier(It.IsAny())).Returns(position); + _ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny())).ReturnsAsync(position); + _ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny())).ReturnsAsync(position); var handler = new ClosePositionCommandHandler( _exchangeService, diff --git a/src/Managing.Application.Workers/Abstractions/IWorkerService.cs b/src/Managing.Application.Workers/Abstractions/IWorkerService.cs index 7063bd3..886a021 100644 --- a/src/Managing.Application.Workers/Abstractions/IWorkerService.cs +++ b/src/Managing.Application.Workers/Abstractions/IWorkerService.cs @@ -8,7 +8,7 @@ public interface IWorkerService Task DisableWorker(WorkerType workerType); Task EnableWorker(WorkerType workerType); Task GetWorker(WorkerType workerType); - IEnumerable GetWorkers(); + Task> GetWorkers(); Task InsertWorker(WorkerType workerType, TimeSpan delay); Task ToggleWorker(WorkerType workerType); Task UpdateWorker(WorkerType workerType, int executionCount); diff --git a/src/Managing.Application.Workers/BaseWorker.cs b/src/Managing.Application.Workers/BaseWorker.cs index c0b7cb9..2d7cd3f 100644 --- a/src/Managing.Application.Workers/BaseWorker.cs +++ b/src/Managing.Application.Workers/BaseWorker.cs @@ -1,4 +1,5 @@ using Managing.Application.Workers.Abstractions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -7,22 +8,22 @@ namespace Managing.Application.Workers; public abstract class BaseWorker : BackgroundService where T : class { + private readonly IServiceProvider _serviceProvider; private readonly WorkerType _workerType; protected readonly ILogger _logger; protected readonly TimeSpan _delay; - private readonly IWorkerService _workerService; private int _executionCount; protected BaseWorker( WorkerType workerType, ILogger logger, TimeSpan timeSpan, - IWorkerService workerService) + IServiceProvider serviceProvider) { _workerType = workerType; _logger = logger; _delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan; - _workerService = workerService; + _serviceProvider = serviceProvider; _executionCount = 0; } @@ -31,28 +32,35 @@ public abstract class BaseWorker : BackgroundService where T : class try { _logger.LogInformation($"[{_workerType}] Starting"); - var worker = await _workerService.GetWorker(_workerType); + using (var scope = _serviceProvider.CreateScope()) + { + var workerService = scope.ServiceProvider.GetRequiredService(); + var worker = await workerService.GetWorker(_workerType); - if (worker == null) - { - await _workerService.InsertWorker(_workerType, _delay); - } - else - { - _logger.LogInformation( - $"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}"); - _executionCount = worker.ExecutionCount; + if (worker == null) + { + await workerService.InsertWorker(_workerType, _delay); + } + else + { + _logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}"); + _executionCount = worker.ExecutionCount; + } } cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping")); while (!cancellationToken.IsCancellationRequested) { - worker = await _workerService.GetWorker(_workerType); + using (var scope = _serviceProvider.CreateScope()) + { + var workerService = scope.ServiceProvider.GetRequiredService(); + var worker = await workerService.GetWorker(_workerType); - await Run(cancellationToken); - _executionCount++; - await _workerService.UpdateWorker(_workerType, _executionCount); + await Run(cancellationToken); + _executionCount++; + await workerService.UpdateWorker(_workerType, _executionCount); + } _logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}"); await Task.Delay(_delay); } diff --git a/src/Managing.Application.Workers/BundleBacktestWorker.cs b/src/Managing.Application.Workers/BundleBacktestWorker.cs index 93da987..457f7d4 100644 --- a/src/Managing.Application.Workers/BundleBacktestWorker.cs +++ b/src/Managing.Application.Workers/BundleBacktestWorker.cs @@ -1,11 +1,11 @@ using System.Text.Json; using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; using Managing.Domain.Strategies; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -16,22 +16,20 @@ namespace Managing.Application.Workers; /// public class BundleBacktestWorker : BaseWorker { - // Removed direct repository usage for bundle requests - private readonly IBacktester _backtester; + private readonly IServiceProvider _serviceProvider; private readonly IMessengerService _messengerService; private static readonly WorkerType _workerType = WorkerType.BundleBacktest; public BundleBacktestWorker( - IBacktester backtester, + IServiceProvider serviceProvider, IMessengerService messengerService, - ILogger logger, - IWorkerService workerService) : base( + ILogger logger) : base( _workerType, logger, TimeSpan.FromMinutes(1), - workerService) + serviceProvider) { - _backtester = backtester; + _serviceProvider = serviceProvider; _messengerService = messengerService; } @@ -42,8 +40,13 @@ public class BundleBacktestWorker : BaseWorker var processingTasks = new List(); 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(); + // Get pending bundle backtest requests - var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending); + var pendingRequests = + await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending); foreach (var bundleRequest in pendingRequests) { @@ -64,9 +67,10 @@ public class BundleBacktestWorker : BaseWorker }, cancellationToken); processingTasks.Add(task); } + await Task.WhenAll(processingTasks); - await RetryUnfinishedBacktestsInFailedBundles(cancellationToken); + await RetryUnfinishedBacktestsInFailedBundles(backtester, cancellationToken); } catch (Exception ex) { @@ -77,13 +81,17 @@ public class BundleBacktestWorker : BaseWorker 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(); + try { _logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId); // Update status to running bundleRequest.Status = BundleBacktestRequestStatus.Running; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); // Deserialize the backtest requests as strongly-typed objects var backtestRequests = @@ -105,10 +113,11 @@ public class BundleBacktestWorker : BaseWorker var runBacktestRequest = backtestRequests[i]; // Update current backtest being processed bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}"; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); // Run the backtest directly with the strongly-typed request - var backtestId = await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken); + var backtestId = await RunSingleBacktest(backtester, runBacktestRequest, bundleRequest, i, + cancellationToken); if (!string.IsNullOrEmpty(backtestId)) { bundleRequest.Results.Add(backtestId); @@ -116,7 +125,7 @@ public class BundleBacktestWorker : BaseWorker // Update progress bundleRequest.CompletedBacktests++; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); _logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}", i + 1, bundleRequest.RequestId); @@ -126,20 +135,16 @@ public class BundleBacktestWorker : BaseWorker _logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}", i + 1, bundleRequest.RequestId); bundleRequest.FailedBacktests++; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); } } - // Update final status + // Update final status and send notifications if (bundleRequest.FailedBacktests == 0) { bundleRequest.Status = BundleBacktestRequestStatus.Completed; // Send Telegram message to the user's channelId - if (bundleRequest.User?.TelegramChannel != null) - { - var message = $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) is completed."; - await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel); - } + await NotifyUser(bundleRequest); } else if (bundleRequest.CompletedBacktests == 0) { @@ -150,11 +155,13 @@ public class BundleBacktestWorker : BaseWorker { bundleRequest.Status = BundleBacktestRequestStatus.Completed; bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed"; + // Send Telegram message to the user's channelId even with partial failures + await NotifyUser(bundleRequest); } bundleRequest.CompletedAt = DateTime.UtcNow; bundleRequest.CurrentBacktest = null; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); _logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}", bundleRequest.RequestId, bundleRequest.Status); @@ -166,12 +173,22 @@ public class BundleBacktestWorker : BaseWorker bundleRequest.Status = BundleBacktestRequestStatus.Failed; bundleRequest.ErrorMessage = ex.Message; bundleRequest.CompletedAt = DateTime.UtcNow; - _backtester.UpdateBundleBacktestRequest(bundleRequest); + await backtester.UpdateBundleBacktestRequestAsync(bundleRequest); + } + } + + private async Task NotifyUser(BundleBacktestRequest bundleRequest) + { + if (bundleRequest.User?.TelegramChannel != null) + { + var message = + $"⚠️ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed with {bundleRequest.FailedBacktests} failed backtests."; + await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel); } } // Change RunSingleBacktest to accept RunBacktestRequest directly - private async Task RunSingleBacktest(RunBacktestRequest runBacktestRequest, + private async Task RunSingleBacktest(IBacktester backtester, RunBacktestRequest runBacktestRequest, BundleBacktestRequest bundleRequest, int index, CancellationToken cancellationToken) { @@ -259,7 +276,7 @@ public class BundleBacktestWorker : BaseWorker }; // Run the backtest (no user context) - var result = await _backtester.RunTradingBotBacktest( + var result = await backtester.RunTradingBotBacktest( backtestConfig, runBacktestRequest.StartDate, runBacktestRequest.EndDate, @@ -275,9 +292,10 @@ public class BundleBacktestWorker : BaseWorker return result.Id; } - private async Task RetryUnfinishedBacktestsInFailedBundles(CancellationToken cancellationToken) + private async Task RetryUnfinishedBacktestsInFailedBundles(IBacktester backtester, + CancellationToken cancellationToken) { - var failedBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Failed); + var failedBundles = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Failed); foreach (var failedBundle in failedBundles) { if (cancellationToken.IsCancellationRequested) @@ -292,20 +310,39 @@ public class BundleBacktestWorker : BaseWorker .Deserialize>(failedBundle.BacktestRequestsJson); if (originalRequests == null) continue; - for (int i = 0; i < originalRequests.Count; i++) + for (int i = failedBundle.CompletedBacktests; i < originalRequests.Count; i++) { var expectedId = /* logic to compute expected backtest id for this request */ string.Empty; // If this backtest was not run or did not succeed, re-run it if (!succeededIds.Contains(expectedId)) { - var backtestId = await RunSingleBacktest(originalRequests[i], failedBundle, i, cancellationToken); + var backtestId = await RunSingleBacktest(backtester, originalRequests[i], failedBundle, i, + cancellationToken); if (!string.IsNullOrEmpty(backtestId)) { failedBundle.Results?.Add(backtestId); - _backtester.UpdateBundleBacktestRequest(failedBundle); + failedBundle.CompletedBacktests++; + await backtester.UpdateBundleBacktestRequestAsync(failedBundle); } } } + + // If all backtests succeeded, update the bundle status + if (failedBundle.CompletedBacktests == originalRequests.Count) + { + failedBundle.Status = BundleBacktestRequestStatus.Completed; + failedBundle.ErrorMessage = null; // Clear any previous error + failedBundle.CompletedAt = DateTime.UtcNow; + await backtester.UpdateBundleBacktestRequestAsync(failedBundle); + + // Notify user about successful retry + await NotifyUser(failedBundle); + } + else + { + _logger.LogWarning("Bundle {RequestId} still has unfinished backtests after retry", + failedBundle.RequestId); + } } } } \ No newline at end of file diff --git a/src/Managing.Application.Workers/FeeWorker.cs b/src/Managing.Application.Workers/FeeWorker.cs deleted file mode 100644 index b8f4648..0000000 --- a/src/Managing.Application.Workers/FeeWorker.cs +++ /dev/null @@ -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 -{ - private readonly ITradingService _tradingService; - private static readonly WorkerType _workerType = WorkerType.Fee; - - public FeeWorker( - ILogger 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); - } -} \ No newline at end of file diff --git a/src/Managing.Application.Workers/FundingRatesWatcher.cs b/src/Managing.Application.Workers/FundingRatesWatcher.cs index 3058b35..649a5f5 100644 --- a/src/Managing.Application.Workers/FundingRatesWatcher.cs +++ b/src/Managing.Application.Workers/FundingRatesWatcher.cs @@ -1,5 +1,4 @@ using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; using Managing.Common; using Microsoft.Extensions.Logging; @@ -11,12 +10,12 @@ public class FundingRatesWatcher : BaseWorker public FundingRatesWatcher( ILogger logger, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( Enums.WorkerType.FundingRatesWatcher, logger, TimeSpan.FromMinutes(30), - workerService + serviceProvider ) { _statisticService = statisticService; diff --git a/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs b/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs index ffbe564..a923f20 100644 --- a/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs +++ b/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs @@ -1,6 +1,7 @@ using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; +using Managing.Core; using Managing.Domain.Backtests; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -11,18 +12,15 @@ namespace Managing.Application.Workers; /// public class GeneticAlgorithmWorker : BaseWorker { - private readonly IGeneticService _geneticService; - private readonly IBacktester _backtester; + private readonly IServiceScopeFactory _scopeFactory; public GeneticAlgorithmWorker( ILogger logger, - IWorkerService workerService, - IGeneticService geneticService, - IBacktester backtester) - : base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), workerService) + IServiceProvider serviceProvider, + IServiceScopeFactory scopeFactory) + : base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), serviceProvider) { - _geneticService = geneticService; - _backtester = backtester; + _scopeFactory = scopeFactory; } protected override async Task Run(CancellationToken cancellationToken) @@ -48,8 +46,9 @@ public class GeneticAlgorithmWorker : BaseWorker { try { - // Get pending genetic requests from the repository - var pendingRequests = _geneticService.GetPendingGeneticRequests(); + // Get pending genetic requests from the repository using scoped service + var pendingRequests = await ServiceScopeHelpers.WithScopedService>(_scopeFactory, + async geneticService => await geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Pending)); if (!pendingRequests.Any()) { @@ -65,21 +64,24 @@ public class GeneticAlgorithmWorker : BaseWorker { _logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId); - // Update status to Running + // Update status to Running using scoped service request.Status = GeneticRequestStatus.Running; - _geneticService.UpdateGeneticRequest(request); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async geneticService => await geneticService.UpdateGeneticRequestAsync(request)); // Run genetic algorithm using the service - var results = await _geneticService.RunGeneticAlgorithm(request, cancellationToken); + var results = await ServiceScopeHelpers.WithScopedServices(_scopeFactory, + async (geneticService, backtester) => await geneticService.RunGeneticAlgorithm(request, cancellationToken)); - // Update request with results + // Update request with results using scoped service request.Status = GeneticRequestStatus.Completed; request.CompletedAt = DateTime.UtcNow; request.BestFitness = results.BestFitness; request.BestIndividual = results.BestIndividual; request.ProgressInfo = results.ProgressInfo; - _geneticService.UpdateGeneticRequest(request); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async geneticService => await geneticService.UpdateGeneticRequestAsync(request)); _logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId); } @@ -88,7 +90,8 @@ public class GeneticAlgorithmWorker : BaseWorker request.Status = GeneticRequestStatus.Failed; request.ErrorMessage = ex.Message; request.CompletedAt = DateTime.UtcNow; - _geneticService.UpdateGeneticRequest(request); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async geneticService => await geneticService.UpdateGeneticRequestAsync(request)); _logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId); } @@ -100,6 +103,4 @@ public class GeneticAlgorithmWorker : BaseWorker throw; } } - - } \ No newline at end of file diff --git a/src/Managing.Application.Workers/LeaderboardWorker.cs b/src/Managing.Application.Workers/LeaderboardWorker.cs index 24c8868..a87775b 100644 --- a/src/Managing.Application.Workers/LeaderboardWorker.cs +++ b/src/Managing.Application.Workers/LeaderboardWorker.cs @@ -1,5 +1,4 @@ using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -12,12 +11,12 @@ public class LeaderboardWorker : BaseWorker public LeaderboardWorker( ILogger logger, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( _workerType, logger, TimeSpan.FromHours(24), - workerService + serviceProvider ) { _statisticService = statisticService; diff --git a/src/Managing.Application.Workers/PricesBaseWorker.cs b/src/Managing.Application.Workers/PricesBaseWorker.cs index 83c90f2..c6032b1 100644 --- a/src/Managing.Application.Workers/PricesBaseWorker.cs +++ b/src/Managing.Application.Workers/PricesBaseWorker.cs @@ -14,7 +14,7 @@ public abstract class PricesBaseWorker : BaseWorker where T : class public PricesBaseWorker( ILogger logger, IPricesService pricesService, - IWorkerService workerService, + IServiceProvider serviceProvider, IStatisticService statisticService, TimeSpan delay, WorkerType workerType, @@ -22,7 +22,7 @@ public abstract class PricesBaseWorker : BaseWorker where T : class workerType, logger, delay, - workerService + serviceProvider ) { _pricesService = pricesService; diff --git a/src/Managing.Application.Workers/PricesFifteenMinutesWorker.cs b/src/Managing.Application.Workers/PricesFifteenMinutesWorker.cs index fc4f63c..8942f6e 100644 --- a/src/Managing.Application.Workers/PricesFifteenMinutesWorker.cs +++ b/src/Managing.Application.Workers/PricesFifteenMinutesWorker.cs @@ -10,11 +10,11 @@ public class PricesFifteenMinutesWorker : PricesBaseWorker logger, IPricesService pricesService, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( logger, pricesService, - workerService, + serviceProvider, statisticService, TimeSpan.FromMinutes(1), WorkerType.PriceFifteenMinutes, diff --git a/src/Managing.Application.Workers/PricesFiveMinutesWorker.cs b/src/Managing.Application.Workers/PricesFiveMinutesWorker.cs index 77a8c1f..59b3905 100644 --- a/src/Managing.Application.Workers/PricesFiveMinutesWorker.cs +++ b/src/Managing.Application.Workers/PricesFiveMinutesWorker.cs @@ -10,11 +10,11 @@ public class PricesFiveMinutesWorker : PricesBaseWorker public PricesFiveMinutesWorker( ILogger logger, IPricesService pricesService, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( logger, pricesService, - workerService, + serviceProvider, statisticService, TimeSpan.FromMinutes(1), WorkerType.PriceFiveMinutes, diff --git a/src/Managing.Application.Workers/PricesFourHoursWorker.cs b/src/Managing.Application.Workers/PricesFourHoursWorker.cs index 50791c7..bd4acc3 100644 --- a/src/Managing.Application.Workers/PricesFourHoursWorker.cs +++ b/src/Managing.Application.Workers/PricesFourHoursWorker.cs @@ -10,11 +10,11 @@ public class PricesFourHoursWorker : PricesBaseWorker public PricesFourHoursWorker( ILogger logger, IPricesService pricesService, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( logger, pricesService, - workerService, + serviceProvider, statisticService, TimeSpan.FromHours(2), WorkerType.PriceFourHour, diff --git a/src/Managing.Application.Workers/PricesOneDayWorker.cs b/src/Managing.Application.Workers/PricesOneDayWorker.cs index b82a9be..60d2954 100644 --- a/src/Managing.Application.Workers/PricesOneDayWorker.cs +++ b/src/Managing.Application.Workers/PricesOneDayWorker.cs @@ -10,11 +10,11 @@ public class PricesOneDayWorker : PricesBaseWorker public PricesOneDayWorker( ILogger logger, IPricesService pricesService, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( logger, pricesService, - workerService, + serviceProvider, statisticService, TimeSpan.FromHours(12), WorkerType.PriceOneDay, diff --git a/src/Managing.Application.Workers/PricesOneHourWorker.cs b/src/Managing.Application.Workers/PricesOneHourWorker.cs index 10f6e34..24b99e8 100644 --- a/src/Managing.Application.Workers/PricesOneHourWorker.cs +++ b/src/Managing.Application.Workers/PricesOneHourWorker.cs @@ -10,11 +10,11 @@ public class PricesOneHourWorker : PricesBaseWorker public PricesOneHourWorker( ILogger logger, IPricesService pricesService, - IStatisticService statisticService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + IStatisticService statisticService) : base( logger, pricesService, - workerService, + serviceProvider, statisticService, TimeSpan.FromMinutes(30), WorkerType.PriceOneHour, diff --git a/src/Managing.Application.Workers/PricesService.cs b/src/Managing.Application.Workers/PricesService.cs index 1f384f9..13b1bbc 100644 --- a/src/Managing.Application.Workers/PricesService.cs +++ b/src/Managing.Application.Workers/PricesService.cs @@ -1,6 +1,7 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Workers.Abstractions; +using Managing.Domain.Accounts; using Managing.Domain.Candles; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -30,8 +31,11 @@ public class PricesService : IPricesService { try { - var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); - + var account = new Account() + { + Exchange = exchange, + }; + if (account == null) throw new Exception($"Enable to found account for exchange {exchange}"); diff --git a/src/Managing.Application.Workers/SpotlightWorker.cs b/src/Managing.Application.Workers/SpotlightWorker.cs index 2d7cd6d..173fa30 100644 --- a/src/Managing.Application.Workers/SpotlightWorker.cs +++ b/src/Managing.Application.Workers/SpotlightWorker.cs @@ -1,5 +1,4 @@ using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; using Managing.Common; using Microsoft.Extensions.Logging; @@ -11,12 +10,12 @@ public class SpotlightWorker : BaseWorker public SpotlightWorker( ILogger logger, - IWorkerService workerService, + IServiceProvider serviceProvider, IStatisticService statisticService) : base( Enums.WorkerType.Spotlight, logger, TimeSpan.FromMinutes(5), - workerService) + serviceProvider) { _statisticService = statisticService; } diff --git a/src/Managing.Application.Workers/StatisticService.cs b/src/Managing.Application.Workers/StatisticService.cs index beba76a..2724d81 100644 --- a/src/Managing.Application.Workers/StatisticService.cs +++ b/src/Managing.Application.Workers/StatisticService.cs @@ -6,7 +6,6 @@ using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; using Managing.Domain.Statistics; -using Managing.Domain.Strategies; using Managing.Domain.Trades; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -55,7 +54,7 @@ public class StatisticService : IStatisticService public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top) { - var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); + var account = (await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == exchange); var date = DateTime.UtcNow; if (account == null) @@ -73,7 +72,7 @@ public class StatisticService : IStatisticService foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker))) { var volume = _exchangeService.GetVolume(account, ticker); - var price = _exchangeService.GetPrice(account, ticker, date); + var price = await _exchangeService.GetPrice(account, ticker, date); volumeTickers.Add(ticker, volume * price); } @@ -138,7 +137,7 @@ public class StatisticService : IStatisticService if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 5m) { await _messengerService.SendFundingRateUpdate(oldRate, newRate); - _statisticRepository.UpdateFundingRate(oldRate, newRate); + await _statisticRepository.UpdateFundingRateAsync(oldRate, newRate); } } } @@ -151,16 +150,21 @@ public class StatisticService : IStatisticService oldRate.Direction == newRate.Direction; } - public Task> GetFundingRates() + public async Task> GetFundingRates() { - var previousFundingRate = _statisticRepository.GetFundingRates(); - return Task.FromResult(previousFundingRate); + var previousFundingRate = await _statisticRepository.GetFundingRatesAsync(); + return previousFundingRate; } public IList GetLastTopVolumeTicker() + { + return GetLastTopVolumeTickerAsync().Result; + } + + public async Task> GetLastTopVolumeTickerAsync() { var from = DateTime.UtcNow.AddDays(-1); - return _statisticRepository.GetTopVolumeTickers(from); + return await _statisticRepository.GetTopVolumeTickersAsync(from); } public async Task> GetTickers() @@ -179,13 +183,14 @@ public class StatisticService : IStatisticService public async Task UpdateSpotlight() { - var scenarios = _tradingService.GetScenarios(); - var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm); + var scenarios = await _tradingService.GetScenariosAsync(); + var account = + (await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm); if (account == null) throw new Exception($"Enable to found default account"); - var overview = GetLastSpotlight(DateTime.Now.AddMinutes(-20)); + var overview = await GetLastSpotlightAsync(DateTime.UtcNow.AddMinutes(-20)); if (overview != null) { @@ -205,7 +210,7 @@ public class StatisticService : IStatisticService overview = new SpotlightOverview { Spotlights = new List(), - DateTime = DateTime.Now, + DateTime = DateTime.UtcNow, Identifier = Guid.NewGuid(), ScenarioCount = scenarios.Count(), }; @@ -225,33 +230,43 @@ public class StatisticService : IStatisticService Scenario = scenario }; - var options = new ParallelOptions() - { - MaxDegreeOfParallelism = 2 - }; + // Use SemaphoreSlim to limit concurrency to 2 operations at a time + using var semaphore = new SemaphoreSlim(2, 2); - _ = Parallel.ForEach(tickers, options, async ticker => + var tickerTasks = tickers.Select(async ticker => { - spotlight.TickerSignals.Add(new TickerSignal + await semaphore.WaitAsync(); + try { - Ticker = ticker, - FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes), - FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes), - OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour), - FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour), - OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay) - }); + var tickerSignal = new TickerSignal + { + Ticker = ticker, + FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes), + FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes), + OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour), + FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour), + OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay) + }; + return tickerSignal; + } + finally + { + semaphore.Release(); + } }); + var tickerSignals = await Task.WhenAll(tickerTasks); + spotlight.TickerSignals.AddRange(tickerSignals.Where(ts => ts != null)); + overview.Spotlights.Add(spotlight); - _statisticRepository.UpdateSpotlightOverview(overview); + await _statisticRepository.UpdateSpotlightOverviewAsync(overview); } - overview.DateTime = DateTime.Now; - _statisticRepository.UpdateSpotlightOverview(overview); + overview.DateTime = DateTime.UtcNow; + await _statisticRepository.UpdateSpotlightOverviewAsync(overview); } - private async Task> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe) + private async Task> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe) { try { @@ -284,8 +299,8 @@ public class StatisticService : IStatisticService var backtest = await _backtester.RunTradingBotBacktest( config, - DateTime.Now.AddDays(-7), - DateTime.Now, + DateTime.UtcNow.AddDays(-7), + DateTime.UtcNow, null, false, false); @@ -300,9 +315,14 @@ public class StatisticService : IStatisticService return null; } - public SpotlightOverview GetLastSpotlight(DateTime dateTime) + public async Task GetLastSpotlight(DateTime dateTime) { - var overviews = _statisticRepository.GetSpotlightOverviews(dateTime); + return await GetLastSpotlightAsync(dateTime); + } + + public async Task GetLastSpotlightAsync(DateTime dateTime) + { + var overviews = await _statisticRepository.GetSpotlightOverviewsAsync(dateTime); if (overviews.Any()) { @@ -314,17 +334,27 @@ public class StatisticService : IStatisticService public List GetBestTraders() { - return _statisticRepository.GetBestTraders(); + return GetBestTradersAsync().Result; + } + + public async Task> GetBestTradersAsync() + { + return await _statisticRepository.GetBestTradersAsync(); } public List GetBadTraders() { - return _statisticRepository.GetBadTraders(); + return GetBadTradersAsync().Result; + } + + public async Task> GetBadTradersAsync() + { + return await _statisticRepository.GetBadTradersAsync(); } public async Task> GetLeadboardPositons() { - var customWatchAccount = _tradingService.GetTradersWatch(); + var customWatchAccount = await _tradingService.GetTradersWatch(); var trades = new List(); foreach (var trader in customWatchAccount) @@ -337,7 +367,7 @@ public class StatisticService : IStatisticService public async Task UpdateLeaderboard() { - var previousBestTraders = _statisticRepository.GetBestTraders(); + var previousBestTraders = await _statisticRepository.GetBestTradersAsync(); var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader(); // Update / Insert best trader @@ -345,7 +375,7 @@ public class StatisticService : IStatisticService { if (previousBestTraders.Exists((p) => p.Address == trader.Address)) { - _statisticRepository.UpdateBestTrader(trader); + await _statisticRepository.UpdateBestTraderAsync(trader); } else { @@ -367,7 +397,7 @@ public class StatisticService : IStatisticService public async Task UpdateNoobiesboard() { - var previousBadTraders = _statisticRepository.GetBadTraders(); + var previousBadTraders = await _statisticRepository.GetBadTradersAsync(); var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader(); // Update / Insert best trader @@ -375,7 +405,7 @@ public class StatisticService : IStatisticService { if (previousBadTraders.Exists((p) => p.Address == trader.Address)) { - _statisticRepository.UpdateBadTrader(trader); + await _statisticRepository.UpdateBadTraderAsync(trader); } else { diff --git a/src/Managing.Application.Workers/TraderWatcher.cs b/src/Managing.Application.Workers/TraderWatcher.cs index 9500a3d..bfeb205 100644 --- a/src/Managing.Application.Workers/TraderWatcher.cs +++ b/src/Managing.Application.Workers/TraderWatcher.cs @@ -1,5 +1,4 @@ using Managing.Application.Abstractions.Services; -using Managing.Application.Workers.Abstractions; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -22,12 +21,12 @@ public class TraderWatcher : BaseWorker /// The worker service to manage worker lifecycle. public TraderWatcher( ILogger logger, - ITradingService tradingService, - IWorkerService workerService) : base( + IServiceProvider serviceProvider, + ITradingService tradingService) : base( _workerType, logger, TimeSpan.FromSeconds(120), - workerService + serviceProvider ) { _tradingService = tradingService; diff --git a/src/Managing.Application.Workers/WorkerService.cs b/src/Managing.Application.Workers/WorkerService.cs index f46fdfc..0d9ea54 100644 --- a/src/Managing.Application.Workers/WorkerService.cs +++ b/src/Managing.Application.Workers/WorkerService.cs @@ -24,7 +24,7 @@ public class WorkerService : IWorkerService var worker = new Worker() { WorkerType = workerType, - StartTime = DateTime.Now, + StartTime = DateTime.UtcNow, LastRunTime = null, ExecutionCount = 0, Delay = delay @@ -47,9 +47,9 @@ public class WorkerService : IWorkerService await _workerRepository.EnableWorker(workerType); } - public IEnumerable GetWorkers() + public async Task> GetWorkers() { - return _workerRepository.GetWorkers(); + return await _workerRepository.GetWorkers(); } public async Task ToggleWorker(Enums.WorkerType workerType) @@ -66,4 +66,4 @@ public class WorkerService : IWorkerService return true; } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/IBotFactory.cs b/src/Managing.Application/Abstractions/IBotFactory.cs index 952c0e6..3ccb9bd 100644 --- a/src/Managing.Application/Abstractions/IBotFactory.cs +++ b/src/Managing.Application/Abstractions/IBotFactory.cs @@ -12,13 +12,13 @@ namespace Managing.Application.Abstractions /// /// The trading bot configuration /// ITradingBot instance - ITradingBot CreateTradingBot(TradingBotConfig config); + Task CreateTradingBot(TradingBotConfig config); /// /// Creates a trading bot for backtesting using the unified TradingBot class /// /// The trading bot configuration /// ITradingBot instance configured for backtesting - ITradingBot CreateBacktestTradingBot(TradingBotConfig config); + Task CreateBacktestTradingBot(TradingBotConfig config); } } \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index b805466..c64f51c 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -7,38 +7,38 @@ namespace Managing.Application.Abstractions; public interface IBotService { - void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data); + Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data); void AddSimpleBotToCache(IBot bot); void AddTradingBotToCache(ITradingBot bot); List GetActiveBots(); - IEnumerable GetSavedBots(); - void StartBotFromBackup(BotBackup backupBot); - BotBackup GetBotBackup(string identifier); + Task> GetSavedBotsAsync(); + Task StartBotFromBackup(BotBackup backupBot); + Task GetBotBackup(string identifier); /// /// Creates a trading bot using the unified TradingBot class /// /// The trading bot configuration /// ITradingBot instance - ITradingBot CreateTradingBot(TradingBotConfig config); + Task CreateTradingBot(TradingBotConfig config); /// /// Creates a trading bot for backtesting using the unified TradingBot class /// /// The trading bot configuration /// ITradingBot instance configured for backtesting - ITradingBot CreateBacktestTradingBot(TradingBotConfig config); + Task CreateBacktestTradingBot(TradingBotConfig config); // Legacy methods - these will use TradingBot internally but maintain backward compatibility - ITradingBot CreateScalpingBot(TradingBotConfig config); - ITradingBot CreateBacktestScalpingBot(TradingBotConfig config); - ITradingBot CreateFlippingBot(TradingBotConfig config); - ITradingBot CreateBacktestFlippingBot(TradingBotConfig config); + Task CreateScalpingBot(TradingBotConfig config); + Task CreateBacktestScalpingBot(TradingBotConfig config); + Task CreateFlippingBot(TradingBotConfig config); + Task CreateBacktestFlippingBot(TradingBotConfig config); IBot CreateSimpleBot(string botName, Workflow workflow); Task StopBot(string botName); Task DeleteBot(string botName); Task RestartBot(string botName); - void ToggleIsForWatchingOnly(string botName); + Task ToggleIsForWatchingOnly(string botName); Task UpdateBotConfiguration(string identifier, TradingBotConfig newConfig); } \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/IScenarioService.cs b/src/Managing.Application/Abstractions/IScenarioService.cs index 5967315..c9708f5 100644 --- a/src/Managing.Application/Abstractions/IScenarioService.cs +++ b/src/Managing.Application/Abstractions/IScenarioService.cs @@ -7,14 +7,10 @@ namespace Managing.Application.Abstractions { public interface IScenarioService { - IEnumerable GetScenarios(); - Scenario CreateScenario(string name, List strategies, int? loopbackPeriod = 1); - IEnumerable GetIndicators(); - bool DeleteStrategy(string name); - bool DeleteScenario(string name); - Scenario GetScenario(string name); + Task CreateScenario(string name, List strategies, int? loopbackPeriod = 1); + Task> GetIndicatorsAsync(); - Indicator CreateStrategy(IndicatorType type, + Task CreateStrategy(IndicatorType type, string name, int? period = null, int? fastPeriods = null, @@ -25,21 +21,19 @@ namespace Managing.Application.Abstractions int? smoothPeriods = null, int? cyclePeriods = null); - bool DeleteStrategies(); - bool DeleteScenarios(); - bool UpdateScenario(string name, List strategies, int? loopbackPeriod); + Task UpdateScenario(string name, List strategies, int? loopbackPeriod); - bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods, + Task UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods); - IEnumerable GetScenariosByUser(User user); - Scenario CreateScenarioForUser(User user, string name, List strategies, int? loopbackPeriod = 1); - IEnumerable GetIndicatorsByUser(User user); - bool DeleteIndicatorByUser(User user, string name); - bool DeleteScenarioByUser(User user, string name); - Scenario GetScenarioByUser(User user, string name); + Task> GetScenariosByUserAsync(User user); + Task CreateScenarioForUser(User user, string name, List strategies, int? loopbackPeriod = 1); + Task> GetIndicatorsByUserAsync(User user); + Task DeleteIndicatorByUser(User user, string name); + Task DeleteScenarioByUser(User user, string name); + Task GetScenarioByUser(User user, string name); - Indicator CreateIndicatorForUser(User user, + Task CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null, @@ -51,11 +45,11 @@ namespace Managing.Application.Abstractions int? smoothPeriods = null, int? cyclePeriods = null); - bool DeleteStrategiesByUser(User user); - bool DeleteScenariosByUser(User user); - bool UpdateScenarioByUser(User user, string name, List strategies, int? loopbackPeriod); + Task DeleteStrategiesByUser(User user); + Task DeleteScenariosByUser(User user); + Task UpdateScenarioByUser(User user, string name, List strategies, int? loopbackPeriod); - bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods, + Task UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods); } diff --git a/src/Managing.Application/Abstractions/ISettingsService.cs b/src/Managing.Application/Abstractions/ISettingsService.cs index f9f2ba8..9cd1cc5 100644 --- a/src/Managing.Application/Abstractions/ISettingsService.cs +++ b/src/Managing.Application/Abstractions/ISettingsService.cs @@ -1,8 +1,10 @@ -namespace Managing.Application.Abstractions; +using Managing.Domain.Users; + +namespace Managing.Application.Abstractions; public interface ISettingsService { - bool SetupSettings(); + Task SetupSettings(); Task ResetSettings(); - Task CreateDefaultConfiguration(Domain.Users.User user); + Task CreateDefaultConfiguration(User user); } diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs index ec4e773..9026ee4 100644 --- a/src/Managing.Application/Abstractions/ITradingBot.cs +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -3,7 +3,6 @@ using Managing.Domain.Accounts; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.Scenarios; -using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; using Managing.Domain.Trades; using static Managing.Common.Enums; @@ -16,7 +15,7 @@ namespace Managing.Application.Abstractions Account Account { get; set; } FixedSizeQueue OptimizedCandles { get; set; } HashSet Candles { get; set; } - HashSet Signals { get; set; } + HashSet Signals { get; set; } List Positions { get; set; } Dictionary WalletBalances { get; set; } Dictionary IndicatorsValues { get; set; } @@ -24,7 +23,7 @@ namespace Managing.Application.Abstractions DateTime CreateDate { get; } DateTime PreloadSince { get; set; } int PreloadedCandlesCount { get; set; } - decimal Fee { get; set; } + Task Run(); Task ToggleIsForWatchOnly(); @@ -36,7 +35,7 @@ namespace Managing.Application.Abstractions Task LoadAccount(); Task 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); /// diff --git a/src/Managing.Application/Accounts/AccountService.cs b/src/Managing.Application/Accounts/AccountService.cs index 6040426..a0107aa 100644 --- a/src/Managing.Application/Accounts/AccountService.cs +++ b/src/Managing.Application/Accounts/AccountService.cs @@ -100,9 +100,18 @@ public class AccountService : IAccountService public async Task GetAccount(string name, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByNameAsync(name); + + if (account == null) + { + throw new ArgumentException($"Account '{name}' not found"); + } + ManageProperties(hideSecrets, getBalance, account); - account.User = await _userRepository.GetUserByNameAsync(account.User.Name); + if (account.User == null && account.User != null) + { + account.User = await _userRepository.GetUserByNameAsync(account.User.Name); + } return account; } @@ -123,9 +132,31 @@ public class AccountService : IAccountService return account; } - public IEnumerable GetAccounts(bool hideSecrets, bool getBalance) + public async Task 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> GetAccounts(bool hideSecrets, bool getBalance) + { + return await GetAccountsAsync(hideSecrets, getBalance); + } + + public async Task> GetAccountsAsync(bool hideSecrets, bool getBalance) + { + var result = await _accountRepository.GetAccountsAsync(); var accounts = new List(); foreach (var account in result) @@ -139,15 +170,21 @@ public class AccountService : IAccountService public IEnumerable GetAccountsByUser(User user, bool hideSecrets = true) { - var cacheKey = $"user-account-{user.Name}"; - - return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); }, - TimeSpan.FromMinutes(5)); + return GetAccountsByUserAsync(user, hideSecrets).Result; } - private IEnumerable GetAccounts(User user, bool hideSecrets, bool getBalance) + public async Task> 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> GetAccountsAsync(User user, bool hideSecrets, bool getBalance) + { + var result = await _accountRepository.GetAccountsAsync(); var accounts = new List(); foreach (var account in result.Where(a => a.User.Name == user.Name)) @@ -161,11 +198,16 @@ public class AccountService : IAccountService public IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets) { - var cacheKey = $"user-account-balance-{user.Name}"; - var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); }, - TimeSpan.FromHours(3)); + return GetAccountsBalancesByUserAsync(user, hideSecrets).Result; + } - return accounts; + public async Task> 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 GetGmxClaimableSummaryAsync(User user, string accountName) @@ -200,7 +242,8 @@ public class AccountService : IAccountService } } - public async Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5) + public async Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, + double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); @@ -220,12 +263,12 @@ public class AccountService : IAccountService { // Call the Web3ProxyService to swap GMX tokens var swapInfos = await _web3ProxyService.SwapGmxTokensAsync( - account.Key, - fromTicker, - toTicker, - amount, - orderType, - triggerRatio, + account.Key, + fromTicker, + toTicker, + amount, + orderType, + triggerRatio, allowedSlippage ); @@ -239,7 +282,8 @@ public class AccountService : IAccountService } } - public async Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null) + public async Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, + decimal amount, int? chainId = null) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); @@ -271,10 +315,10 @@ public class AccountService : IAccountService { // Call the Web3ProxyService to send tokens var swapInfos = await _web3ProxyService.SendTokenAsync( - account.Key, - recipientAddress, - ticker, - amount, + account.Key, + recipientAddress, + ticker, + amount, chainId ); diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index 3fa6d45..d0ec0b4 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -121,6 +121,12 @@ namespace Managing.Application.Backtesting result.StartDate = startDate; result.EndDate = endDate; + // Ensure RequestId is set - required for PostgreSQL NOT NULL constraint + if (string.IsNullOrEmpty(result.RequestId)) + { + result.RequestId = Guid.NewGuid().ToString(); + } + if (save && user != null) { _backtestRepository.InsertBacktestForUser(user, result); @@ -138,8 +144,9 @@ namespace Managing.Application.Backtesting var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user); if (refundSuccess) { - _logger.LogInformation( - "Successfully refunded credits for user {UserName} after backtest failure", user.Name); + _logger.LogError( + "Successfully refunded credits for user {UserName} after backtest failure: {message}", + user.Name, ex.Message); } else { @@ -188,7 +195,7 @@ namespace Managing.Application.Backtesting string requestId = null, object metadata = null) { - var tradingBot = _botFactory.CreateBacktestTradingBot(config); + var tradingBot = await _botFactory.CreateBacktestTradingBot(config); // Scenario and indicators should already be loaded in constructor by BotService // This is just a validation check to ensure everything loaded properly @@ -215,26 +222,7 @@ namespace Managing.Application.Backtesting private async Task GetAccountFromConfig(TradingBotConfig config) { - var accounts = _accountService.GetAccounts(false, false).ToArray(); - var account = accounts.FirstOrDefault(a => - a.Name.Equals(config.AccountName, StringComparison.OrdinalIgnoreCase) && - a.Exchange == TradingExchanges.GmxV2); - - if (account == null && accounts.Any()) - { - account = accounts.First(); - } - - if (account != null) - { - return account; - } - - return new Account - { - Name = config.AccountName, - Exchange = TradingExchanges.GmxV2 - }; + return await _accountService.GetAccountByAccountName(config.AccountName, false, false); } private List GetCandles(Ticker ticker, Timeframe timeframe, @@ -270,13 +258,13 @@ namespace Managing.Application.Backtesting _logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}", totalCandles, config.Ticker, config.Timeframe); - bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance); + bot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance); foreach (var candle in candles) { bot.OptimizedCandles.Enqueue(candle); bot.Candles.Add(candle); - bot.Run(); + await bot.Run(); currentCandle++; @@ -318,8 +306,6 @@ namespace Managing.Application.Backtesting var finalPnl = bot.GetProfitAndLoss(); var winRate = bot.GetWinRate(); - var optimizedMoneyManagement = - TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement); var stats = TradingHelpers.GetStatistics(bot.WalletBalances); var growthPercentage = TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl); @@ -357,7 +343,6 @@ namespace Managing.Application.Backtesting Fees = fees, WalletBalances = bot.WalletBalances.ToList(), Statistics = stats, - OptimizedMoneyManagement = optimizedMoneyManagement, IndicatorsValues = withCandles ? AggregateValues(indicatorsValues, bot.IndicatorsValues) : new Dictionary(), @@ -442,11 +427,11 @@ namespace Managing.Application.Backtesting return indicatorsValues; } - public bool DeleteBacktest(string id) + public async Task DeleteBacktestAsync(string id) { try { - _backtestRepository.DeleteBacktestByIdForUser(null, id); + await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id); return true; } catch (Exception ex) @@ -476,12 +461,24 @@ namespace Managing.Application.Backtesting return backtests; } + public async Task> GetBacktestsByUserAsync(User user) + { + var backtests = await _backtestRepository.GetBacktestsByUserAsync(user); + return backtests; + } + public IEnumerable GetBacktestsByRequestId(string requestId) { var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList(); return backtests; } + public async Task> GetBacktestsByRequestIdAsync(string requestId) + { + var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId); + return backtests; + } + public (IEnumerable Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc") { @@ -490,9 +487,19 @@ namespace Managing.Application.Backtesting return (backtests, totalCount); } - public Backtest GetBacktestByIdForUser(User user, string id) + public async Task<(IEnumerable 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 GetBacktestByIdForUserAsync(User user, string id) + { + var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id); if (backtest == null) return null; @@ -504,12 +511,12 @@ namespace Managing.Application.Backtesting var account = new Account { Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm }; - var candles = _exchangeService.GetCandlesInflux( + var candles = await _exchangeService.GetCandlesInflux( account.Exchange, backtest.Config.Ticker, backtest.StartDate, backtest.Config.Timeframe, - backtest.EndDate).Result; + backtest.EndDate); if (candles != null && candles.Count > 0) { @@ -525,11 +532,11 @@ namespace Managing.Application.Backtesting return backtest; } - public bool DeleteBacktestByUser(User user, string id) + public async Task DeleteBacktestByUserAsync(User user, string id) { try { - _backtestRepository.DeleteBacktestByIdForUser(user, id); + await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id); return true; } catch (Exception ex) @@ -539,11 +546,11 @@ namespace Managing.Application.Backtesting } } - public bool DeleteBacktestsByIdsForUser(User user, IEnumerable ids) + public async Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable ids) { try { - _backtestRepository.DeleteBacktestsByIdsForUser(user, ids); + await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids); return true; } catch (Exception ex) @@ -567,11 +574,11 @@ namespace Managing.Application.Backtesting } } - public bool DeleteBacktestsByRequestId(string requestId) + public async Task DeleteBacktestsByRequestIdAsync(string requestId) { try { - _backtestRepository.DeleteBacktestsByRequestId(requestId); + await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId); return true; } catch (Exception ex) @@ -589,6 +596,14 @@ namespace Managing.Application.Backtesting return (backtests, totalCount); } + public async Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync( + User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc") + { + var (backtests, totalCount) = + await _backtestRepository.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder); + return (backtests, totalCount); + } + // Bundle backtest methods public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest) { @@ -600,27 +615,53 @@ namespace Managing.Application.Backtesting return _backtestRepository.GetBundleBacktestRequestsByUser(user); } + public async Task> GetBundleBacktestRequestsByUserAsync(User user) + { + return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user); + } + public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id) { return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id); } + public async Task GetBundleBacktestRequestByIdForUserAsync(User user, string id) + { + return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id); + } + public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest) { _backtestRepository.UpdateBundleBacktestRequest(bundleRequest); } + public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest) + { + await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest); + } + public void DeleteBundleBacktestRequestByIdForUser(User user, string id) { _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id); } + public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id) + { + await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id); + } + public IEnumerable GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status) { // Use the repository method to get all bundles, then filter by status return _backtestRepository.GetBundleBacktestRequestsByStatus(status); } + public async Task> GetBundleBacktestRequestsByStatusAsync( + BundleBacktestRequestStatus status) + { + return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status); + } + /// /// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request. /// diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs index 787e6bd..9ef577c 100644 --- a/src/Managing.Application/Bots/Base/BotFactory.cs +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -1,5 +1,6 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; +using Managing.Application.ManageBot; using Managing.Domain.Bots; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ namespace Managing.Application.Bots.Base private readonly ILogger _tradingBotLogger; private readonly ITradingService _tradingService; private readonly IBotService _botService; + private readonly IBackupBotService _backupBotService; public BotFactory( IExchangeService exchangeService, @@ -21,7 +23,8 @@ namespace Managing.Application.Bots.Base IMessengerService messengerService, IAccountService accountService, ITradingService tradingService, - IBotService botService) + IBotService botService, + IBackupBotService backupBotService) { _tradingBotLogger = tradingBotLogger; _exchangeService = exchangeService; @@ -29,23 +32,24 @@ namespace Managing.Application.Bots.Base _accountService = accountService; _tradingService = tradingService; _botService = botService; + _backupBotService = backupBotService; } IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow) { - return new SimpleBot(botName, _tradingBotLogger, workflow, _botService); + return new SimpleBot(botName, _tradingBotLogger, workflow, _botService, _backupBotService); } - ITradingBot IBotFactory.CreateTradingBot(TradingBotConfig config) + public async Task CreateTradingBot(TradingBotConfig config) { // Delegate to BotService which handles scenario loading properly - return _botService.CreateTradingBot(config); + return await _botService.CreateTradingBot(config); } - ITradingBot IBotFactory.CreateBacktestTradingBot(TradingBotConfig config) + public async Task CreateBacktestTradingBot(TradingBotConfig config) { // Delegate to BotService which handles scenario loading properly - return _botService.CreateBacktestTradingBot(config); + return await _botService.CreateBacktestTradingBot(config); } } } \ No newline at end of file diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs index 368ce53..11d2b07 100644 --- a/src/Managing.Application/Bots/SimpleBot.cs +++ b/src/Managing.Application/Bots/SimpleBot.cs @@ -1,4 +1,5 @@ using Managing.Application.Abstractions; +using Managing.Application.ManageBot; using Managing.Domain.Bots; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; @@ -10,13 +11,16 @@ namespace Managing.Application.Bots { public readonly ILogger Logger; private readonly IBotService _botService; + private readonly IBackupBotService _backupBotService; private Workflow _workflow; - public SimpleBot(string name, ILogger logger, Workflow workflow, IBotService botService) : + public SimpleBot(string name, ILogger logger, Workflow workflow, IBotService botService, + IBackupBotService backupBotService) : base(name) { Logger = logger; _botService = botService; + _backupBotService = backupBotService; _workflow = workflow; Interval = 100; } @@ -35,20 +39,20 @@ namespace Managing.Application.Bots Logger.LogInformation(Identifier); Logger.LogInformation(DateTime.Now.ToString()); await _workflow.Execute(); - SaveBackup(); + await SaveBackup(); Logger.LogInformation("__________________________________________________"); }); } - public override void SaveBackup() + public override async Task SaveBackup() { var data = JsonConvert.SerializeObject(_workflow); - _botService.SaveOrUpdateBotBackup(User, Identifier, Status, data); + await _backupBotService.SaveOrUpdateBotBackup(User, Identifier, Status, new TradingBotBackup()); } public override void LoadBackup(BotBackup backup) { - _workflow = JsonConvert.DeserializeObject(backup.Data); + _workflow = new Workflow(); } } } \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index d0ef473..e6f1605 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -1,8 +1,10 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; +using Managing.Application.ManageBot; using Managing.Application.Trading; using Managing.Application.Trading.Commands; using Managing.Common; +using Managing.Core; using Managing.Core.FixedSizedQueue; using Managing.Domain.Accounts; using Managing.Domain.Bots; @@ -11,7 +13,9 @@ using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; +using Managing.Domain.Synth.Models; using Managing.Domain.Trades; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using static Managing.Common.Enums; @@ -21,42 +25,30 @@ namespace Managing.Application.Bots; public class TradingBot : Bot, ITradingBot { public readonly ILogger Logger; - public readonly IExchangeService ExchangeService; - public readonly IMessengerService MessengerService; - public readonly IAccountService AccountService; - private readonly ITradingService TradingService; - private readonly IBotService BotService; + private readonly IServiceScopeFactory _scopeFactory; public TradingBotConfig Config { get; set; } public Account Account { get; set; } public HashSet Indicators { get; set; } public FixedSizeQueue OptimizedCandles { get; set; } public HashSet Candles { get; set; } - public HashSet Signals { get; set; } + public HashSet Signals { get; set; } public List Positions { get; set; } public Dictionary WalletBalances { get; set; } public Dictionary IndicatorsValues { get; set; } - public DateTime StartupTime { get; set; } public DateTime PreloadSince { get; set; } public int PreloadedCandlesCount { get; set; } - public decimal Fee { get; set; } + + public int _maxSignals = 10; // Maximum number of signals to keep in memory public TradingBot( - IExchangeService exchangeService, ILogger logger, - ITradingService tradingService, - IAccountService accountService, - IMessengerService messengerService, - IBotService botService, + IServiceScopeFactory scopeFactory, TradingBotConfig config ) : base(config.Name) { - ExchangeService = exchangeService; - AccountService = accountService; - MessengerService = messengerService; - TradingService = tradingService; - BotService = botService; + _scopeFactory = scopeFactory; Logger = logger; if (config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) @@ -69,7 +61,7 @@ public class TradingBot : Bot, ITradingBot Config = config; Indicators = new HashSet(); - Signals = new HashSet(); + Signals = new HashSet(); OptimizedCandles = new FixedSizeQueue(600); Candles = new HashSet(); Positions = new List(); @@ -97,8 +89,6 @@ public class TradingBot : Bot, ITradingBot public override void Start() { base.Start(); - // Load account synchronously - LoadAccount().GetAwaiter().GetResult(); if (!Config.IsForBacktest) { @@ -110,74 +100,69 @@ public class TradingBot : Bot, ITradingBot "Scenario or indicators not loaded properly in constructor. This indicates a configuration error."); } - PreloadCandles().GetAwaiter().GetResult(); - CancelAllOrders().GetAwaiter().GetResult(); - - // Send startup message only for fresh starts (not reboots) - // Consider it a reboot if the bot was created more than 5 minutes ago - var timeSinceCreation = DateTime.UtcNow - CreateDate; - var isReboot = timeSinceCreation.TotalMinutes > 3; - - - if (!isReboot) + // Start async initialization in the background without blocking + Task.Run(async () => { try { - StartupTime = DateTime.UtcNow; + // Load account asynchronously + await LoadAccount(); - var indicatorNames = Indicators.Select(i => i.Type.ToString()).ToList(); - var startupMessage = $"🚀 **Bot Started Successfully!**\n\n" + - $"📊 **Trading Setup:**\n" + - $"🎯 Ticker: `{Config.Ticker}`\n" + - $"⏰ Timeframe: `{Config.Timeframe}`\n" + - $"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" + - $"💰 Balance: `${Config.BotTradingBalance:F2}`\n" + - $"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" + - $"📈 **Active Indicators:** `{string.Join(", ", indicatorNames)}`\n\n" + - $"✅ Ready to monitor signals and execute trades!\n" + - $"📢 I'll notify you when signals are triggered."; + // Preload candles and cancel orders + await PreloadCandles(); + await CancelAllOrders(); - LogInformation(startupMessage).GetAwaiter().GetResult(); + // Send startup message only for fresh starts (not reboots) + var timeSinceCreation = DateTime.UtcNow - CreateDate; + var isReboot = timeSinceCreation.TotalMinutes > 3; + + if (!isReboot) + { + StartupTime = DateTime.UtcNow; + + var indicatorNames = Indicators.Select(i => i.Type.ToString()).ToList(); + var startupMessage = $"🚀 **Bot Started Successfully!**\n\n" + + $"📊 **Trading Setup:**\n" + + $"🎯 Ticker: `{Config.Ticker}`\n" + + $"⏰ Timeframe: `{Config.Timeframe}`\n" + + $"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" + + $"💰 Balance: `${Config.BotTradingBalance:F2}`\n" + + $"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" + + $"📈 **Active Indicators:** `{string.Join(", ", indicatorNames)}`\n\n" + + $"✅ Ready to monitor signals and execute trades!\n" + + $"📢 I'll notify you when signals are triggered."; + + await LogInformation(startupMessage); + } + else + { + await LogInformation($"🔄 **Bot Restarted**\n" + + $"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" + + $"✅ Ready to continue trading"); + } + + // Initialize the worker after everything is loaded + await InitWorker(Run); } catch (Exception ex) { - Logger.LogError(ex, ex.Message); + Logger.LogError(ex, "Error during bot startup: {Message}", ex.Message); + Stop(); // Stop the bot if initialization fails } - } - else - { - try - { - LogInformation($"🔄 **Bot Restarted**\n" + - $"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" + - $"✅ Ready to continue trading").GetAwaiter().GetResult(); - } - catch (Exception ex) - { - Logger.LogError(ex, ex.Message); - } - } - - - InitWorker(Run).GetAwaiter().GetResult(); + }); } - // Fee = TradingService.GetFee(Account, IsForBacktest); + } public async Task LoadAccount() { if (Config.IsForBacktest) return; - var account = await AccountService.GetAccount(Config.AccountName, false, false); - if (account == null) - { - Logger.LogWarning($"No account found for this {Config.AccountName}"); - Stop(); - } - else + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async accountService => { + var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false); Account = account; - } + }); } @@ -231,19 +216,18 @@ public class TradingBot : Bot, ITradingBot if (!Config.IsForBacktest) { // Check broker balance before running - var balance = await ExchangeService.GetBalance(Account, false); - if (balance < Constants.GMX.Config.MinimumPositionAmount && Positions.All(p => p.IsFinished())) + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => { - await LogWarning( - $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot {Identifier} and saving backup."); - SaveBackup(); - Stop(); - return; - } - - Logger.LogInformation($"____________________{Name}____________________"); - Logger.LogInformation( - $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Ticker : {Config.Ticker}"); + var balance = await exchangeService.GetBalance(Account, false); + if (balance < Constants.GMX.Config.MinimumPositionAmount && Positions.All(p => p.IsFinished())) + { + await LogWarning( + $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot {Identifier} and saving backup."); + await SaveBackup(); + Stop(); + return; + } + }); } var previousLastCandle = OptimizedCandles.LastOrDefault(); @@ -263,7 +247,7 @@ public class TradingBot : Bot, ITradingBot if (!Config.IsForBacktest) { - SaveBackup(); + await SaveBackup(); UpdateIndicatorsValues(); } @@ -294,31 +278,35 @@ public class TradingBot : Bot, ITradingBot var haveSignal = Signals.Any(); if (haveSignal) { - if (Signals.Count > 10) - PreloadSince = Signals.TakeLast(10).First().Date; + if (Signals.Count > _maxSignals) + PreloadSince = Signals.TakeLast(_maxSignals).First().Date; else PreloadSince = Signals.First().Date; } - if (Positions.Any()) + if (Positions.Any() && PreloadSince > Positions.FirstOrDefault()?.Open.Date) PreloadSince = Positions.First().Open.Date; - var candles = - await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, PreloadSince, Config.Timeframe); - - foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => { - if (!OptimizedCandles.Any(c => c.Date == candle.Date)) - { - OptimizedCandles.Enqueue(candle); - Candles.Add(candle); + var candles = + await exchangeService.GetCandlesInflux(TradingExchanges.Evm, Config.Ticker, PreloadSince, + Config.Timeframe); - if (!haveSignal) + foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + { + if (!OptimizedCandles.Any(c => c.Date == candle.Date)) { - await UpdateSignals(OptimizedCandles); + OptimizedCandles.Enqueue(candle); + Candles.Add(candle); + + if (!haveSignal) + { + await UpdateSignals(OptimizedCandles); + } } } - } + }); PreloadedCandlesCount = OptimizedCandles.Count(); } @@ -328,14 +316,20 @@ public class TradingBot : Bot, ITradingBot // If position open and not flipped, do not update signals if (!Config.FlipPosition && Positions.Any(p => !p.IsFinished())) return; + // Check if we're in cooldown period for any direction + if (IsInCooldownPeriod()) + { + // Still in cooldown period, skip signal generation + return; + } + var signal = TradingBox.GetSignal(candles.ToHashSet(), Indicators, Signals, Config.Scenario.LoopbackPeriod); if (signal == null) return; - signal.User = Account.User; await AddSignal(signal); } - private async Task AddSignal(Signal signal) + private async Task AddSignal(LightSignal signal) { if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest)) signal.Status = SignalStatus.Expired; @@ -349,56 +343,60 @@ public class TradingBot : Bot, ITradingBot $"🔍 Indicators: `{string.Join(", ", indicatorNames)}`\n" + $"🆔 Signal ID: `{signal.Identifier}`"; - // Apply Synth-based signal filtering if enabled - if (Config.UseSynthApi || !Config.IsForBacktest) + if ((Config.UseSynthApi || !Config.IsForBacktest) && ExecutionCount > 0) { - var currentPrice = Config.IsForBacktest - ? OptimizedCandles.Last().Close - : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + await ServiceScopeHelpers.WithScopedServices(_scopeFactory, + async (tradingService, exchangeService) => + { + var currentPrice = Config.IsForBacktest + ? OptimizedCandles.Last().Close + : await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); - var signalValidationResult = TradingService.ValidateSynthSignalAsync(signal, currentPrice, Config, - Config.IsForBacktest).GetAwaiter().GetResult(); + var signalValidationResult = await tradingService.ValidateSynthSignalAsync(signal, currentPrice, + Config, + Config.IsForBacktest); - if (signalValidationResult.Confidence == Confidence.None || - signalValidationResult.Confidence == Confidence.Low || - signalValidationResult.IsBlocked) - { - // TODO : remove this when Synth is stable - // signal.Status = SignalStatus.Expired; - signalText += $"\n\n🚫 *Synth Signal Filter*\n" + - $"Signal `{signal.Identifier}` blocked by Synth risk assessment\n\n" + - $"📊 *Risk Analysis Details*\n" + - $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + - $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + - $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + - $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + - $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + - $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + - $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + - $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + - $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + - $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + - $"📋 *Context*\n`{signalValidationResult.ValidationContext}`"; - } - else - { - signal.SetConfidence(signalValidationResult.Confidence); - signalText += $"\n\n✅ *Synth Risk Assessment Passed*\n" + - $"Confidence: `{signalValidationResult.Confidence}`\n\n" + - $"📊 *Risk Analysis Details*\n" + - $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + - $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + - $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + - $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + - $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + - $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + - $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + - $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + - $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + - $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + - $"📋 *Context*\n`{signalValidationResult.ValidationContext}`"; - } + if (signalValidationResult.Confidence == Confidence.None || + signalValidationResult.Confidence == Confidence.Low || + signalValidationResult.IsBlocked) + { + // TODO : remove this when Synth is stable + // signal.Status = SignalStatus.Expired; + signalText += $"\n\n🚫 *Synth Signal Filter*\n" + + $"Signal `{signal.Identifier}` blocked by Synth risk assessment\n\n" + + $"📊 *Risk Analysis Details*\n" + + $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + + $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + + $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + + $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + + $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + + $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + + $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + + $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + + $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + + $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + + $"📋 *Context*\n`{signalValidationResult.ValidationContext}`"; + } + else + { + signal.Confidence = signalValidationResult.Confidence; + signalText += $"\n\n✅ *Synth Risk Assessment Passed*\n" + + $"Confidence: `{signalValidationResult.Confidence}`\n\n" + + $"📊 *Risk Analysis Details*\n" + + $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + + $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + + $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + + $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + + $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + + $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + + $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + + $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + + $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + + $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + + $"📋 *Context*\n`{signalValidationResult.ValidationContext}`"; + } + }); } Signals.Add(signal); @@ -407,8 +405,11 @@ public class TradingBot : Bot, ITradingBot if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0) { - await MessengerService.SendSignal(signalText, Account.Exchange, Config.Ticker, signal.Direction, - Config.Timeframe); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async messengerService => + { + await messengerService.SendSignal(signalText, Account.Exchange, Config.Ticker, signal.Direction, + Config.Timeframe); + }); } } @@ -418,17 +419,21 @@ public class TradingBot : Bot, ITradingBot return; var lastCandle = OptimizedCandles.Last(); - var newCandle = - await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, lastCandle.Date, Config.Timeframe); - - foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => { - OptimizedCandles.Enqueue(candle); - Candles.Add(candle); - } + var newCandle = + await exchangeService.GetCandlesInflux(TradingExchanges.Evm, Config.Ticker, lastCandle.Date, + Config.Timeframe); + + foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + { + OptimizedCandles.Enqueue(candle); + Candles.Add(candle); + } + }); } - private async Task RecreateSignalFromPosition(Position position) + private async Task RecreateSignalFromPosition(Position position) { try { @@ -444,13 +449,13 @@ public class TradingBot : Bot, ITradingBot } // Create a new signal based on position information - var recreatedSignal = new Signal( + var recreatedSignal = new LightSignal( ticker: Config.Ticker, direction: position.OriginDirection, confidence: Confidence.Medium, // Default confidence for recreated signals candle: positionCandle, date: position.Open.Date, - exchange: Account.Exchange, + exchange: TradingExchanges.Evm, indicatorType: IndicatorType.Stc, // Use a valid strategy type for recreated signals signalType: SignalType.Signal, indicatorName: "RecreatedSignal" @@ -462,7 +467,6 @@ public class TradingBot : Bot, ITradingBot position.SignalIdentifier = recreatedSignal.Identifier; recreatedSignal.Status = SignalStatus.PositionOpen; - recreatedSignal.User = Account.User; // Add the recreated signal to our collection Signals.Add(recreatedSignal); @@ -480,6 +484,7 @@ public class TradingBot : Bot, ITradingBot private async Task ManagePositions() { + // First, process all existing positions that are not finished foreach (var position in Positions.Where(p => !p.IsFinished())) { var signalForPosition = Signals.FirstOrDefault(s => s.Identifier == position.SignalIdentifier); @@ -509,10 +514,36 @@ public class TradingBot : Bot, ITradingBot await UpdatePosition(signalForPosition, position); } - // Open position for signals waiting for a position open - foreach (var signal in Signals.Where(s => s.Status == SignalStatus.WaitingForPosition)) + // Then, open positions for signals waiting for a position open + // But first, check if we already have a position for any of these signals + var signalsWaitingForPosition = Signals.Where(s => s.Status == SignalStatus.WaitingForPosition).ToList(); + + foreach (var signal in signalsWaitingForPosition) { - Task.Run(() => OpenPosition(signal)).GetAwaiter().GetResult(); + // Check if we already have a position for this signal (in case it was added but not processed yet) + var existingPosition = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier); + + if (existingPosition != null) + { + // Position already exists for this signal, update signal status + await LogInformation( + $"🔄 **Signal Status Update**\nSignal: `{signal.Identifier}`\nStatus: `{signal.Status}` → `PositionOpen`\nPosition already exists: `{existingPosition.Identifier}`"); + SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); + continue; + } + + // No existing position found, proceed to open a new one + var newlyCreatedPosition = await OpenPosition(signal); + + if (newlyCreatedPosition != null) + { + Positions.Add(newlyCreatedPosition); + } + else + { + await LogWarning( + $"⚠️ **Position Creation Failed**\nSignal: `{signal.Identifier}`\nPosition creation returned null"); + } } } @@ -537,17 +568,31 @@ public class TradingBot : Bot, ITradingBot } } - private async Task UpdatePosition(Signal signal, Position positionForSignal) + private async Task UpdatePosition(LightSignal signal, Position positionForSignal) { try { - var position = Config.IsForBacktest - ? positionForSignal - : TradingService.GetPositionByIdentifier(positionForSignal.Identifier); + Position position = null; + List positionsExchange = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async tradingService => + { + position = Config.IsForBacktest + ? positionForSignal + : await tradingService.GetPositionByIdentifierAsync(positionForSignal.Identifier); - var positionsExchange = Config.IsForBacktest - ? new List { position } - : await TradingService.GetBrokerPositions(Account); + if (Config.IsForBacktest) + { + positionsExchange = new List { position }; + } + else + { + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + positionsExchange = (await exchangeService.GetBrokerPositions(Account)).ToList(); + }); + } + }); if (!Config.IsForBacktest) { @@ -565,10 +610,8 @@ public class TradingBot : Bot, ITradingBot } else { - // No position, position close on the broker if (!position.Status.Equals(PositionStatus.New)) { - // Setup the previous status of the position position.Status = PositionStatus.Filled; } } @@ -576,10 +619,11 @@ public class TradingBot : Bot, ITradingBot if (position.Status == PositionStatus.New) { - var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); + List orders = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => { orders = await exchangeService.GetOpenOrders(Account, Config.Ticker); }); if (orders.Any()) { - // If there are 3 or more orders and position is still not filled, check if enough time has passed if (orders.Count() >= 3) { var currentTime = Config.IsForBacktest ? OptimizedCandles.Last().Date : DateTime.UtcNow; @@ -590,10 +634,13 @@ public class TradingBot : Bot, ITradingBot { await LogWarning( $"⚠️ **Order Cleanup**\nToo many open orders: `{orders.Count()}`\nPosition: `{positionForSignal.Identifier}`\nTime elapsed: `{waitTimeMinutes}min`\nCanceling all orders..."); - try { - await ExchangeService.CancelOrder(Account, Config.Ticker); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + await exchangeService.CancelOrder(Account, Config.Ticker); + }); await LogInformation( $"✅ **Orders Canceled**\nSuccessfully canceled all orders for: `{Config.Ticker}`"); } @@ -626,21 +673,19 @@ public class TradingBot : Bot, ITradingBot await HandleClosedPosition(positionForSignal); } } - else if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped)) + else if (position.Status == PositionStatus.Finished || position.Status == PositionStatus.Flipped) { await HandleClosedPosition(positionForSignal); } - else if (position.Status == (PositionStatus.Filled | PositionStatus.PartiallyFilled)) + else if (position.Status == PositionStatus.Filled || position.Status == PositionStatus.PartiallyFilled) { - // For backtesting or force close if not executed on exchange : - // check if position is still open - // Check status, if still open update the status of the position - - // Position might be partially filled, meaning that TPSL havent been sended yet - // But the position might already been closed by the exchange so we have to check should be closed - var lastCandle = Config.IsForBacktest - ? OptimizedCandles.Last() - : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); + Candle lastCandle = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => + { + lastCandle = Config.IsForBacktest + ? OptimizedCandles.Last() + : exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); + }); var currentTime = Config.IsForBacktest ? lastCandle.Date : DateTime.UtcNow; var currentPnl = positionForSignal.ProfitAndLoss?.Realized ?? 0; @@ -649,7 +694,6 @@ public class TradingBot : Bot, ITradingBot 2) : 0; - // Check if position is in profit by comparing entry price with current market price var isPositionInProfit = positionForSignal.OriginDirection == TradeDirection.Long ? lastCandle.Close > positionForSignal.Open.Price : lastCandle.Close < positionForSignal.Open.Price; @@ -657,11 +701,8 @@ public class TradingBot : Bot, ITradingBot var hasExceededTimeLimit = Config.MaxPositionTimeHours.HasValue && HasPositionExceededTimeLimit(positionForSignal, currentTime); - // 2. Time-based closure (if time limit exceeded) if (hasExceededTimeLimit) { - // If CloseEarlyWhenProfitable is enabled, only close if profitable - // If CloseEarlyWhenProfitable is disabled, close regardless of profit status var shouldCloseOnTimeLimit = !Config.CloseEarlyWhenProfitable || isPositionInProfit; if (shouldCloseOnTimeLimit) @@ -673,18 +714,8 @@ public class TradingBot : Bot, ITradingBot await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true); return; } - else - { - // Only send this message every 10 executions to avoid spam - // if (ExecutionCount % 50 == 0) - // { - // await LogInformation( - // $"⏳ **Time Limit - Waiting**\nTime limit exceeded but position at loss\n📉 Entry: `${positionForSignal.Open.Price}` → Current: `${lastCandle.Close}`\n💰 Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)\n🎯 Waiting for profit before closing (CloseEarlyWhenProfitable enabled)\n📊 Message sent every 10 executions to reduce spam"); - // } - } } - // 3. Normal stop loss and take profit checks if (positionForSignal.OriginDirection == TradeDirection.Long) { if (positionForSignal.StopLoss.Price >= lastCandle.Low) @@ -742,11 +773,9 @@ public class TradingBot : Bot, ITradingBot } } } - else if (position.Status == (PositionStatus.Rejected | PositionStatus.Canceled)) + else if (position.Status == PositionStatus.Rejected || position.Status == PositionStatus.Canceled) { await LogWarning($"Open position trade is rejected for signal {signal.Identifier}"); - // if position is not open - // Re-open the trade for the signal only if signal still up if (signal.Status == SignalStatus.PositionOpen) { Logger.LogInformation($"Try to re-open position"); @@ -754,20 +783,28 @@ public class TradingBot : Bot, ITradingBot } } - // Synth-based position monitoring for liquidation risk if (Config.UseSynthApi && !Config.IsForBacktest && positionForSignal.Status == PositionStatus.Filled) { - var currentPrice = ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); - var riskResult = await TradingService.MonitorSynthPositionRiskAsync( - Config.Ticker, - positionForSignal.OriginDirection, - currentPrice, - positionForSignal.StopLoss.Price, - positionForSignal.Identifier, - Config); + decimal currentPrice = 0; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + currentPrice = await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + }); + var riskResult = default(SynthRiskResult); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async tradingService => + { + riskResult = await tradingService.MonitorSynthPositionRiskAsync( + Config.Ticker, + positionForSignal.OriginDirection, + currentPrice, + positionForSignal.StopLoss.Price, + positionForSignal.Identifier, + Config); + }); - if (riskResult != null && riskResult.ShouldWarn && !string.IsNullOrEmpty(riskResult.WarningMessage)) + if (riskResult.ShouldWarn && !string.IsNullOrEmpty(riskResult.WarningMessage)) { await LogWarning(riskResult.WarningMessage); } @@ -788,46 +825,43 @@ public class TradingBot : Bot, ITradingBot } catch (Exception ex) { - await LogWarning($"Cannot update position {positionForSignal.Identifier}: {ex.Message}"); + await LogWarning($"Cannot update position {positionForSignal.Identifier}: {ex.Message}, {ex.StackTrace}"); SentrySdk.CaptureException(ex); return; } } - private async Task OpenPosition(Signal signal) + private async Task OpenPosition(LightSignal signal) { - // Check if a position is already open Logger.LogInformation($"Opening position for {signal.Identifier}"); - var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled - && p.SignalIdentifier != signal.Identifier); + // Check for any existing open position (not finished) for this ticker + var openedPosition = Positions.FirstOrDefault(p => !p.IsFinished() && p.SignalIdentifier != signal.Identifier); - var lastPrice = Config.IsForBacktest - ? OptimizedCandles.Last().Close - : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + decimal lastPrice = await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + return Config.IsForBacktest + ? OptimizedCandles.Last().Close + : await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + }); - // If position open if (openedPosition != null) { var previousSignal = Signals.First(s => s.Identifier == openedPosition.SignalIdentifier); - // Check if signal is the opposite side => flip the position if (openedPosition.OriginDirection == signal.Direction) { await LogInformation( $"📍 **Same Direction Signal**\nSignal `{signal.Identifier}` tried to open position\nBut `{previousSignal.Identifier}` already open for same direction"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); + return null; } else { - // An operation is already open for the opposite direction - // ==> Flip the position if (Config.FlipPosition) { - // Check if current position is in profit before flipping var isPositionInProfit = (openedPosition.ProfitAndLoss?.Realized ?? 0) > 0; - - // Determine if we should flip based on configuration var shouldFlip = !Config.FlipOnlyWhenInProfit || isPositionInProfit; if (shouldFlip) @@ -840,9 +874,10 @@ public class TradingBot : Bot, ITradingBot $"🔄 **Position Flip Initiated**\nFlipping position due to opposite signal\nReason: {flipReason}"); await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped); - await OpenPosition(signal); + var newPosition = await OpenPosition(signal); await LogInformation( $"✅ **Position Flipped**\nPosition: `{previousSignal.Identifier}` → `{signal.Identifier}`\nPrice: `${lastPrice}`"); + return newPosition; } else { @@ -851,7 +886,7 @@ public class TradingBot : Bot, ITradingBot $"💸 **Flip Blocked - Not Profitable**\nPosition `{previousSignal.Identifier}` PnL: `${currentPnl:F2}`\nSignal `{signal.Identifier}` will wait for profitability"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); - return; + return null; } } else @@ -859,15 +894,17 @@ public class TradingBot : Bot, ITradingBot await LogInformation( $"🚫 **Flip Disabled**\nPosition already open for: `{previousSignal.Identifier}`\nFlipping disabled, new signal expired"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); + return null; } } } else { - if (!(await CanOpenPosition(signal))) + bool canOpen = await CanOpenPosition(signal); + if (!canOpen) { SetSignalStatus(signal.Identifier, SignalStatus.Expired); - return; + return null; } await LogInformation( @@ -888,45 +925,53 @@ public class TradingBot : Bot, ITradingBot lastPrice, signalIdentifier: signal.Identifier); - var position = (new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService) - .Handle(command)).GetAwaiter().GetResult(); + var position = await ServiceScopeHelpers + .WithScopedServices( + _scopeFactory, + async (exchangeService, accountService, tradingService) => + { + return await new OpenPositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(command); + }); if (position != null) { - Positions.Add(position); - if (position.Open.Status != TradeStatus.Cancelled) { SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); if (!Config.IsForBacktest) { - await MessengerService.SendPosition(position); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async messengerService => { await messengerService.SendPosition(position); }); } Logger.LogInformation($"Position requested"); + return position; // Return the created position without adding to list } else { await SetPositionStatus(signal.Identifier, PositionStatus.Rejected); SetSignalStatus(signal.Identifier, SignalStatus.Expired); + return null; } } + + return null; } catch (Exception ex) { - // Keep signal open for debug purpose - //SetSignalStatus(signal.Identifier, SignalStatus.Expired); SetSignalStatus(signal.Identifier, SignalStatus.Expired); await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}"); + return null; } } } - private async Task CanOpenPosition(Signal signal) + private async Task CanOpenPosition(LightSignal signal) { // Early return if we're in backtest mode and haven't executed yet - if (!Config.IsForBacktest && ExecutionCount < 1) + if (!Config.IsForBacktest && ExecutionCount == 0) { await LogInformation("⏳ **Bot Not Ready**\nCannot open position\nBot hasn't executed first cycle yet"); return false; @@ -935,7 +980,7 @@ public class TradingBot : Bot, ITradingBot // Check if we're in backtest mode if (Config.IsForBacktest) { - return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal); + return !IsInCooldownPeriod() && await CheckLossStreak(signal); } // Check broker positions for live trading @@ -948,22 +993,33 @@ public class TradingBot : Bot, ITradingBot // Synth-based pre-trade risk assessment if (Config.UseSynthApi) { - var currentPrice = Config.IsForBacktest - ? OptimizedCandles.Last().Close - : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + decimal currentPrice = 0; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => + { + currentPrice = Config.IsForBacktest + ? OptimizedCandles.Last().Close + : await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + }); - if (!(await TradingService.AssessSynthPositionRiskAsync(Config.Ticker, signal.Direction, currentPrice, - Config, Config.IsForBacktest))) + + bool synthRisk = false; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async tradingService => + { + synthRisk = await tradingService.AssessSynthPositionRiskAsync(Config.Ticker, signal.Direction, + currentPrice, + Config, Config.IsForBacktest); + }); + if (!synthRisk) { return false; } } // Check cooldown period and loss streak - return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal); + return !IsInCooldownPeriod() && await CheckLossStreak(signal); } - private async Task CheckLossStreak(Signal signal) + private async Task CheckLossStreak(LightSignal signal) { // If MaxLossStreak is 0, there's no limit if (Config.MaxLossStreak <= 0) @@ -1007,7 +1063,9 @@ public class TradingBot : Bot, ITradingBot { try { - var positions = await ExchangeService.GetBrokerPositions(Account); + List positions = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => { positions = (await exchangeService.GetBrokerPositions(Account)).ToList(); }); if (!positions.Any(p => p.Ticker == Config.Ticker)) { return true; @@ -1015,7 +1073,12 @@ public class TradingBot : Bot, ITradingBot // Handle existing position on broker var previousPosition = Positions.LastOrDefault(); - var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); + List orders = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + orders = (await exchangeService.GetOpenOrders(Account, Config.Ticker)).ToList(); + }); var reason = $"Cannot open position. There is already a position open for {Config.Ticker} on the broker."; @@ -1039,41 +1102,6 @@ public class TradingBot : Bot, ITradingBot } } - private async Task CheckCooldownPeriod(Signal signal) - { - var lastPosition = Positions.LastOrDefault(p => p.IsFinished() - && p.SignalIdentifier != signal.Identifier - && p.OriginDirection == signal.Direction); - - if (lastPosition == null) - { - return true; - } - - var cooldownCandle = OptimizedCandles.TakeLast((int)Config.CooldownPeriod).FirstOrDefault(); - if (cooldownCandle == null) - { - await LogWarning("📊 **Cooldown Check Failed**\nCannot check cooldown\nNot enough candles available"); - return false; - } - - // Get the actual closing date of the position instead of signal date - var positionClosingDate = GetPositionClosingDate(lastPosition); - if (positionClosingDate == null) - { - await LogWarning($"Cannot determine closing date for last position {lastPosition.Identifier}"); - return false; - } - - var canOpenPosition = positionClosingDate < cooldownCandle.Date; - if (!canOpenPosition) - { - await LogInformation( - $"⏳ **Cooldown Active**\nPosition blocked by cooldown period\n📅 Last Position Closed: `{positionClosingDate:MM/dd HH:mm}`\n🕒 Cooldown Until: `{cooldownCandle.Date:MM/dd HH:mm}`"); - } - - return canOpenPosition; - } /// /// Gets the actual closing date of a position by checking which trade (Stop Loss or Take Profit) was executed. @@ -1118,7 +1146,7 @@ public class TradingBot : Bot, ITradingBot return availableDates.Any() ? availableDates.Max() : position.Open.Date; } - public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, + public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false) { if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && @@ -1131,8 +1159,19 @@ public class TradingBot : Bot, ITradingBot await LogInformation( $"🔧 **Closing Trade**\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n📋 Type: `{tradeToClose.TradeType}`\n📊 Quantity: `{tradeToClose.Quantity}`\n🎯 Closing Position: `{(tradeClosingPosition ? "Yes" : "No")}`"); + decimal quantity = 0; + + if (!Config.IsForBacktest) + { + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + quantity = await exchangeService.GetQuantityInPosition(Account, Config.Ticker); + }); + } + // Get status of position before closing it. The position might be already close by the exchange - if (!Config.IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Config.Ticker) == 0) + if (!Config.IsForBacktest && quantity == 0) { Logger.LogInformation($"Trade already close on exchange"); await HandleClosedPosition(position); @@ -1142,10 +1181,16 @@ public class TradingBot : Bot, ITradingBot var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest); try { - var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService) - .Handle(command)).Result; + Position closedPosition = null; + await ServiceScopeHelpers.WithScopedServices( + _scopeFactory, async (exchangeService, accountService, tradingService) => + { + closedPosition = + await new ClosePositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(command); + }); - if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped)) + if (closedPosition.Status == PositionStatus.Finished || closedPosition.Status == PositionStatus.Flipped) { if (tradeClosingPosition) { @@ -1163,7 +1208,7 @@ public class TradingBot : Bot, ITradingBot { await LogWarning($"Position {signal.Identifier} not closed : {ex.Message}"); - if (position.Status == (PositionStatus.Canceled | PositionStatus.Rejected)) + if (position.Status == PositionStatus.Canceled || position.Status == PositionStatus.Rejected) { // Trade close on exchange => Should close trade manually await SetPositionStatus(signal.Identifier, PositionStatus.Finished); @@ -1176,17 +1221,24 @@ public class TradingBot : Bot, ITradingBot { if (Positions.Any(p => p.Identifier == position.Identifier)) { - // Update the close date for the trade that actually closed the position - var currentCandle = Config.IsForBacktest - ? OptimizedCandles.LastOrDefault() - : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); + Candle currentCandle = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => + { + currentCandle = Config.IsForBacktest + ? OptimizedCandles.LastOrDefault() + : exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); + }); if (currentCandle != null) { - // Get the last 4 candles to check if SL/TP was actually hit - var recentCandles = Config.IsForBacktest - ? OptimizedCandles.TakeLast(4).ToList() - : await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, DateTime.UtcNow.AddHours(-4), Config.Timeframe); + List recentCandles = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => + { + recentCandles = Config.IsForBacktest + ? OptimizedCandles.TakeLast(4).ToList() + : (await exchangeService.GetCandlesInflux(TradingExchanges.Evm, Config.Ticker, + DateTime.UtcNow.AddHours(-4), Config.Timeframe)).ToList(); + }); var minPriceRecent = recentCandles.Min(c => c.Low); var maxPriceRecent = recentCandles.Max(c => c.High); @@ -1196,13 +1248,11 @@ public class TradingBot : Bot, ITradingBot if (position.OriginDirection == TradeDirection.Long) { - // For long positions: SL hit if recent low touched SL, TP hit if recent high touched TP wasStopLossHit = minPriceRecent <= position.StopLoss.Price; wasTakeProfitHit = maxPriceRecent >= position.TakeProfit1.Price; } else { - // For short positions: SL hit if recent high touched SL, TP hit if recent low touched TP wasStopLossHit = maxPriceRecent >= position.StopLoss.Price; wasTakeProfitHit = minPriceRecent <= position.TakeProfit1.Price; } @@ -1211,10 +1261,9 @@ public class TradingBot : Bot, ITradingBot if (wasStopLossHit) { - // Position was closed by Stop Loss execution closingPrice = position.StopLoss.Price; position.StopLoss.SetDate(currentCandle.Date); - + Logger.LogInformation( $"🛑 **Stop Loss Execution Confirmed**\n" + $"Position: `{position.Identifier}`\n" + @@ -1223,10 +1272,9 @@ public class TradingBot : Bot, ITradingBot } else if (wasTakeProfitHit) { - // Position was closed by Take Profit execution closingPrice = position.TakeProfit1.Price; position.TakeProfit1.SetDate(currentCandle.Date); - + Logger.LogInformation( $"🎯 **Take Profit Execution Confirmed**\n" + $"Position: `{position.Identifier}`\n" + @@ -1235,16 +1283,19 @@ public class TradingBot : Bot, ITradingBot } else { - // Manual close or exchange close - use current market price - closingPrice = Config.IsForBacktest - ? currentCandle.Close - : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); - - // Determine if it was profitable to set the correct trade type for tracking - bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long + closingPrice = Config.IsForBacktest + ? currentCandle.Close + : 0; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + closingPrice = await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + }); + + bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long ? closingPrice > position.Open.Price : closingPrice < position.Open.Price; - + if (isManualCloseProfitable) { position.TakeProfit1.SetDate(currentCandle.Date); @@ -1253,7 +1304,7 @@ public class TradingBot : Bot, ITradingBot { position.StopLoss.SetDate(currentCandle.Date); } - + Logger.LogInformation( $"✋ **Manual/Exchange Close Detected**\n" + $"Position: `{position.Identifier}`\n" + @@ -1262,10 +1313,9 @@ public class TradingBot : Bot, ITradingBot $"Closing at market price: `${closingPrice:F2}`"); } - // Calculate PnL based on actual closing price var entryPrice = position.Open.Price; var positionSize = position.Open.Quantity * position.Open.Leverage; - + decimal pnl; if (position.OriginDirection == TradeDirection.Long) { @@ -1275,8 +1325,7 @@ public class TradingBot : Bot, ITradingBot { pnl = (entryPrice - closingPrice) * positionSize; } - - // Update position's realized PnL if not already set + if (position.ProfitAndLoss == null) { position.ProfitAndLoss = new ProfitAndLoss { Realized = pnl }; @@ -1291,10 +1340,8 @@ public class TradingBot : Bot, ITradingBot Logger.LogInformation( $"✅ **Position Closed Successfully**\nPosition: `{position.SignalIdentifier}`\nPnL: `${position.ProfitAndLoss?.Realized:F2}`"); - // Update the bot's trading balance after position is closed if (position.ProfitAndLoss != null) { - // Add PnL (could be positive or negative) Config.BotTradingBalance += position.ProfitAndLoss.Realized; Logger.LogInformation( @@ -1308,7 +1355,8 @@ public class TradingBot : Bot, ITradingBot if (!Config.IsForBacktest) { - await MessengerService.SendClosingPosition(position); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async messengerService => { await messengerService.SendClosingPosition(position); }); } await CancelAllOrders(); @@ -1320,11 +1368,21 @@ public class TradingBot : Bot, ITradingBot { try { - var openOrders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); + List openOrders = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + openOrders = (await exchangeService.GetOpenOrders(Account, Config.Ticker)).ToList(); + }); if (openOrders.Any()) { - var openPositions = (await ExchangeService.GetBrokerPositions(Account)) - .Where(p => p.Ticker == Config.Ticker); + List openPositions = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + openPositions = (await exchangeService.GetBrokerPositions(Account)) + .Where(p => p.Ticker == Config.Ticker).ToList(); + }); var cancelClose = openPositions.Any(); if (cancelClose) @@ -1334,9 +1392,14 @@ public class TradingBot : Bot, ITradingBot else { Logger.LogInformation($"Canceling all orders for {Config.Ticker}"); - await ExchangeService.CancelOrder(Account, Config.Ticker); - var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Config.Ticker); - Logger.LogInformation($"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}"); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + await exchangeService.CancelOrder(Account, Config.Ticker); + var closePendingOrderStatus = await exchangeService.CancelOrder(Account, Config.Ticker); + Logger.LogInformation( + $"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}"); + }); } } else @@ -1486,6 +1549,10 @@ public class TradingBot : Bot, ITradingBot private async Task LogInformation(string message) { Logger.LogInformation(message); + + if (Config.IsForBacktest) + return; + try { await SendTradeMessage(message); @@ -1517,27 +1584,34 @@ public class TradingBot : Bot, ITradingBot { var user = Account.User; var messageWithBotName = $"🤖 **{user.AgentName} - {Name}**\n{message}"; - await MessengerService.SendTradeMessage(messageWithBotName, isBadBehavior, user); + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async messengerService => + { + await messengerService.SendTradeMessage(messageWithBotName, isBadBehavior, user); + }); } } - public override void SaveBackup() + public override async Task SaveBackup() { var data = new TradingBotBackup { Config = Config, - Signals = Signals, + Signals = Signals.TakeLast(_maxSignals).ToHashSet(), Positions = Positions, WalletBalances = WalletBalances, StartupTime = StartupTime, CreateDate = CreateDate }; - BotService.SaveOrUpdateBotBackup(User, Identifier, Status, JsonConvert.SerializeObject(data)); + + // Use ServiceScopeHelpers for thread safety and DRY code + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async backBotService => { await backBotService.SaveOrUpdateBotBackup(User, Identifier, Status, data); }); } public override void LoadBackup(BotBackup backup) { - var data = JsonConvert.DeserializeObject(backup.Data); + var data = backup.Data; // Load the configuration directly Config = data.Config; @@ -1546,7 +1620,7 @@ public class TradingBot : Bot, ITradingBot Config.IsForBacktest = false; // Load runtime state - Signals = data.Signals ?? new HashSet(); + Signals = data.Signals ?? new HashSet(); Positions = data.Positions ?? new List(); WalletBalances = data.WalletBalances ?? new Dictionary(); PreloadSince = data.StartupTime; @@ -1573,20 +1647,17 @@ public class TradingBot : Bot, ITradingBot } // Create a fake signal for manual position opening - var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, + var signal = new LightSignal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, TradingExchanges.GmxV2, IndicatorType.Stc, SignalType.Signal, "Manual Signal"); signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct - signal.User = Account.User; // Assign user // Add the signal to our collection await AddSignal(signal); // Open the position using the generated signal (SL/TP handled by MoneyManagement) - await OpenPosition(signal); + var position = await OpenPosition(signal); - // Get the opened position - var position = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier); if (position == null) { // Clean up the signal if position creation failed @@ -1594,8 +1665,11 @@ public class TradingBot : Bot, ITradingBot throw new Exception("Failed to open position"); } + // Add the position to the list after successful creation + Positions.Add(position); + Logger.LogInformation( - $"👤 **Manual Position Opened**\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`"); + $"👤 **Manual Position Opened**\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`\nAdded to positions list"); return position; } @@ -1866,7 +1940,7 @@ public class TradingBot : Bot, ITradingBot // Save the updated configuration as backup if (!Config.IsForBacktest) { - SaveBackup(); + await SaveBackup(); } return true; @@ -1967,37 +2041,50 @@ public class TradingBot : Bot, ITradingBot return changes; } -} - -public class TradingBotBackup -{ - /// - /// The complete trading bot configuration - /// - public TradingBotConfig Config { get; set; } /// - /// Runtime state: Active signals for the bot + /// Checks if the bot is currently in a cooldown period for any direction. /// - public HashSet Signals { get; set; } + /// True if in cooldown period for any direction, false otherwise + private bool IsInCooldownPeriod() + { + var lastPosition = Positions.LastOrDefault(p => p.IsFinished()); - /// - /// Runtime state: Open and closed positions for the bot - /// - public List Positions { get; set; } + if (lastPosition == null) + { + return false; // No previous position, no cooldown + } - /// - /// Runtime state: Historical wallet balances over time - /// - public Dictionary WalletBalances { get; set; } + var cooldownCandle = OptimizedCandles.TakeLast((int)Config.CooldownPeriod).FirstOrDefault(); + if (cooldownCandle == null) + { + return false; // Not enough candles to determine cooldown + } - /// - /// Runtime state: When the bot was started - /// - public DateTime StartupTime { get; set; } + var positionClosingDate = GetPositionClosingDate(lastPosition); + if (positionClosingDate == null) + { + return false; // Cannot determine closing date + } - /// - /// Runtime state: When the bot was created - /// - public DateTime CreateDate { get; set; } + var isInCooldown = positionClosingDate >= cooldownCandle.Date; + + if (isInCooldown) + { + var intervalMilliseconds = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe); + var intervalMinutes = intervalMilliseconds / (1000.0 * 60.0); // Convert milliseconds to minutes + var cooldownEndTime = cooldownCandle.Date.AddMinutes(intervalMinutes * Config.CooldownPeriod); + var remainingTime = cooldownEndTime - DateTime.UtcNow; + + Logger.LogWarning( + $"⏳ **Cooldown Period Active**\n" + + $"Cannot open new positions\n" + + $"Last position closed: `{positionClosingDate:HH:mm:ss}`\n" + + $"Cooldown period: `{Config.CooldownPeriod}` candles\n" + + $"Cooldown ends: `{cooldownEndTime:HH:mm:ss}`\n" + + $"Remaining time: `{remainingTime.TotalMinutes:F1} minutes`"); + } + + return isInCooldown; + } } \ No newline at end of file diff --git a/src/Managing.Application/GeneticService.cs b/src/Managing.Application/GeneticService.cs index 4913098..e167df5 100644 --- a/src/Managing.Application/GeneticService.cs +++ b/src/Managing.Application/GeneticService.cs @@ -2,12 +2,14 @@ using System.Text.Json; using GeneticSharp; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; +using Managing.Core; using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; using Managing.Domain.Risk; using Managing.Domain.Scenarios; using Managing.Domain.Users; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -22,6 +24,7 @@ public class GeneticService : IGeneticService private readonly IBacktester _backtester; private readonly ILogger _logger; private readonly IMessengerService _messengerService; + private readonly IServiceScopeFactory _serviceScopeFactory; // Predefined parameter ranges for each indicator (matching backtestGenetic.tsx) public static readonly Dictionary ParameterRanges = new() @@ -188,12 +191,14 @@ public class GeneticService : IGeneticService IGeneticRepository geneticRepository, IBacktester backtester, ILogger logger, - IMessengerService messengerService) + IMessengerService messengerService, + IServiceScopeFactory serviceScopeFactory) { _geneticRepository = geneticRepository; _backtester = backtester; _logger = logger; _messengerService = messengerService; + _serviceScopeFactory = serviceScopeFactory; } public GeneticRequest CreateGeneticRequest( @@ -247,9 +252,9 @@ public class GeneticService : IGeneticService return _geneticRepository.GetGeneticRequestByIdForUser(user, id); } - public void UpdateGeneticRequest(GeneticRequest geneticRequest) + public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest) { - _geneticRepository.UpdateGeneticRequest(geneticRequest); + await _geneticRepository.UpdateGeneticRequestAsync(geneticRequest); } public void DeleteGeneticRequestByIdForUser(User user, string id) @@ -257,9 +262,9 @@ public class GeneticService : IGeneticService _geneticRepository.DeleteGeneticRequestByIdForUser(user, id); } - public IEnumerable GetPendingGeneticRequests() + public Task> GetGeneticRequestsAsync(GeneticRequestStatus status) { - return _geneticRepository.GetPendingGeneticRequests(); + return _geneticRepository.GetGeneticRequestsAsync(status); } /// @@ -277,7 +282,7 @@ public class GeneticService : IGeneticService // Update status to running request.Status = GeneticRequestStatus.Running; - UpdateGeneticRequest(request); + await UpdateGeneticRequestAsync(request); // Create or resume chromosome for trading bot configuration TradingBotChromosome chromosome; @@ -307,7 +312,7 @@ public class GeneticService : IGeneticService } // Create fitness function first - var fitness = new TradingBotFitness(_backtester, request, _logger); + var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger); // Create genetic algorithm with better configuration var ga = new GeneticAlgorithm( @@ -341,7 +346,7 @@ public class GeneticService : IGeneticService // Run the genetic algorithm with periodic checks for cancellation var generationCount = 0; - ga.GenerationRan += (sender, e) => + ga.GenerationRan += async (sender, e) => { generationCount = ga.GenerationsNumber; @@ -362,7 +367,7 @@ public class GeneticService : IGeneticService request.BestChromosome = JsonSerializer.Serialize(geneValues); } - UpdateGeneticRequest(request); + await UpdateGeneticRequestAsync(request); // Check for cancellation if (cancellationToken.IsCancellationRequested) @@ -381,7 +386,7 @@ public class GeneticService : IGeneticService // Update request status to pending so it can be resumed request.Status = GeneticRequestStatus.Pending; - UpdateGeneticRequest(request); + await UpdateGeneticRequestAsync(request); return new GeneticAlgorithmResult { @@ -413,7 +418,7 @@ public class GeneticService : IGeneticService completed_at = DateTime.UtcNow }); - UpdateGeneticRequest(request); + await UpdateGeneticRequestAsync(request); // Send notification about the completed genetic algorithm try @@ -442,7 +447,7 @@ public class GeneticService : IGeneticService request.Status = GeneticRequestStatus.Failed; request.ErrorMessage = ex.Message; request.CompletedAt = DateTime.UtcNow; - UpdateGeneticRequest(request); + await UpdateGeneticRequestAsync(request); throw; } @@ -505,7 +510,7 @@ public class TradingBotChromosome : ChromosomeBase private readonly List _eligibleIndicators; private readonly double _maxTakeProfit; private readonly Random _random = new Random(); - private int[]? _indicatorSelectionPattern; + private int[] _indicatorSelectionPattern; // Gene structure: // 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak) @@ -552,7 +557,7 @@ public class TradingBotChromosome : ChromosomeBase GenerateIndicatorSelectionPattern(); } - return new Gene(_indicatorSelectionPattern![geneIndex - 5]); + return new Gene(_indicatorSelectionPattern[geneIndex - 5]); } else { @@ -790,7 +795,7 @@ public class TradingBotChromosome : ChromosomeBase return _random.Next((int)range.min, (int)range.max + 1); } - private string? GetParameterName(int index) + private string GetParameterName(int index) { return index switch { @@ -879,14 +884,14 @@ public class GeneticIndicator /// public class TradingBotFitness : IFitness { - private readonly IBacktester _backtester; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly GeneticRequest _request; private GeneticAlgorithm _geneticAlgorithm; private readonly ILogger _logger; - public TradingBotFitness(IBacktester backtester, GeneticRequest request, ILogger logger) + public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request, ILogger logger) { - _backtester = backtester; + _serviceScopeFactory = serviceScopeFactory; _request = request; _logger = logger; } @@ -909,19 +914,22 @@ public class TradingBotFitness : IFitness // Get current generation number (default to 0 if not available) var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0; - // Run backtest - var backtest = _backtester.RunTradingBotBacktest( - config, - _request.StartDate, - _request.EndDate, - _request.User, - true, - false, // Don't include candles - _request.RequestId, - new - { - generation = currentGeneration - } + // Run backtest using scoped service to avoid DbContext concurrency issues + var backtest = ServiceScopeHelpers.WithScopedService( + _serviceScopeFactory, + backtester => backtester.RunTradingBotBacktest( + config, + _request.StartDate, + _request.EndDate, + _request.User, + true, + false, // Don't include candles + _request.RequestId, + new + { + generation = currentGeneration + } + ) ).Result; // Calculate multi-objective fitness based on backtest results @@ -929,8 +937,9 @@ public class TradingBotFitness : IFitness return fitness; } - catch (Exception) + catch (Exception ex) { + _logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message); // Return low fitness for failed backtests return 0.1; } diff --git a/src/Managing.Application/ManageBot/BackupBotService.cs b/src/Managing.Application/ManageBot/BackupBotService.cs new file mode 100644 index 0000000..9524c8c --- /dev/null +++ b/src/Managing.Application/ManageBot/BackupBotService.cs @@ -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 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 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); + } + } + } +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index ffe52a1..acf0dbc 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -6,8 +6,8 @@ using Managing.Application.Bots; using Managing.Domain.Bots; using Managing.Domain.Users; using Managing.Domain.Workflows; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using static Managing.Common.Enums; namespace Managing.Application.ManageBot @@ -22,13 +22,16 @@ namespace Managing.Application.ManageBot private readonly ITradingService _tradingService; private readonly IMoneyManagementService _moneyManagementService; private readonly IUserService _userService; + private readonly IBackupBotService _backupBotService; + private readonly IServiceScopeFactory _scopeFactory; private ConcurrentDictionary _botTasks = new ConcurrentDictionary(); public BotService(IBotRepository botRepository, IExchangeService exchangeService, IMessengerService messengerService, IAccountService accountService, ILogger tradingBotLogger, - ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService) + ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService, + IBackupBotService backupBotService, IServiceScopeFactory scopeFactory) { _botRepository = botRepository; _exchangeService = exchangeService; @@ -38,35 +41,8 @@ namespace Managing.Application.ManageBot _tradingService = tradingService; _moneyManagementService = moneyManagementService; _userService = userService; - } - - public BotBackup GetBotBackup(string identifier) - { - return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier); - } - - public void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data) - { - var backup = GetBotBackup(identifier); - - if (backup != null) - { - backup.LastStatus = status; - backup.Data = data; - _botRepository.UpdateBackupBot(backup); - } - else - { - var botBackup = new BotBackup - { - LastStatus = status, - User = user, - Identifier = identifier, - Data = data - }; - - _botRepository.InsertBotAsync(botBackup); - } + _backupBotService = backupBotService; + _scopeFactory = scopeFactory; } public class BotTaskWrapper @@ -96,6 +72,27 @@ namespace Managing.Application.ManageBot _botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask); } + + private async Task InitBot(ITradingBot bot, BotBackup backupBot) + { + var user = await _userService.GetUser(backupBot.User.Name); + bot.User = user; + // Config is already set correctly from backup data, so we only need to restore signals, positions, etc. + bot.LoadBackup(backupBot); + + // Only start the bot if the backup status is Up + if (backupBot.LastStatus == BotStatus.Up) + { + // Start the bot asynchronously without waiting for completion + _ = Task.Run(() => bot.Start()); + } + else + { + // Keep the bot in Down status if it was originally Down + bot.Stop(); + } + } + public List GetActiveBots() { var bots = _botTasks.Values @@ -107,17 +104,17 @@ namespace Managing.Application.ManageBot return bots; } - public IEnumerable GetSavedBots() + public async Task> GetSavedBotsAsync() { - return _botRepository.GetBots(); + return await _botRepository.GetBotsAsync(); } - public void StartBotFromBackup(BotBackup backupBot) + public async Task StartBotFromBackup(BotBackup backupBot) { object bot = null; Task botTask = null; - var scalpingBotData = JsonConvert.DeserializeObject(backupBot.Data); + var scalpingBotData = backupBot.Data; // Get the config directly from the backup var scalpingConfig = scalpingBotData.Config; @@ -137,7 +134,7 @@ namespace Managing.Application.ManageBot // Ensure the scenario is properly loaded from database if needed if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName); if (scenario != null) { scalpingConfig.Scenario = scenario; @@ -158,7 +155,7 @@ namespace Managing.Application.ManageBot // Ensure critical properties are set correctly for restored bots scalpingConfig.IsForBacktest = false; - bot = CreateTradingBot(scalpingConfig); + bot = await CreateTradingBot(scalpingConfig); botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); if (bot != null && botTask != null) @@ -168,29 +165,38 @@ namespace Managing.Application.ManageBot } } - private void InitBot(ITradingBot bot, BotBackup backupBot) + public async Task GetBotBackup(string identifier) { - var user = _userService.GetUser(backupBot.User.Name); - bot.User = user; - // Config is already set correctly from backup data, so we only need to restore signals, positions, etc. - bot.LoadBackup(backupBot); + return await _botRepository.GetBotByIdentifierAsync(identifier); + } - // Only start the bot if the backup status is Up - if (backupBot.LastStatus == BotStatus.Up) + public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data) + { + var backup = await GetBotBackup(identifier); + + if (backup != null) { - // Start the bot asynchronously without waiting for completion - _ = Task.Run(() => bot.Start()); + backup.LastStatus = status; + backup.Data = data; + await _botRepository.UpdateBackupBot(backup); } else { - // Keep the bot in Down status if it was originally Down - bot.Stop(); + var botBackup = new BotBackup + { + LastStatus = status, + User = user, + Identifier = identifier, + Data = data + }; + + await _botRepository.InsertBotAsync(botBackup); } } public IBot CreateSimpleBot(string botName, Workflow workflow) { - return new SimpleBot(botName, _tradingBotLogger, workflow, this); + return new SimpleBot(botName, _tradingBotLogger, workflow, this, _backupBotService); } public async Task StopBot(string identifier) @@ -263,7 +269,7 @@ namespace Managing.Application.ManageBot // Restart the bot (this will update StartupTime) bot.Restart(); - + // Start the bot asynchronously without waiting for completion _ = Task.Run(() => bot.Start()); @@ -282,12 +288,12 @@ namespace Managing.Application.ManageBot return BotStatus.Down.ToString(); } - public void ToggleIsForWatchingOnly(string identifier) + public async Task ToggleIsForWatchingOnly(string identifier) { if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) && botTaskWrapper.BotInstance is ITradingBot tradingBot) { - tradingBot.ToggleIsForWatchOnly().Wait(); + await tradingBot.ToggleIsForWatchOnly(); } } @@ -305,7 +311,7 @@ namespace Managing.Application.ManageBot // Ensure the scenario is properly loaded from database if needed if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName); if (scenario != null) { newConfig.Scenario = scenario; @@ -365,12 +371,12 @@ namespace Managing.Application.ManageBot } - public ITradingBot CreateTradingBot(TradingBotConfig config) + public async Task CreateTradingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -386,22 +392,15 @@ namespace Managing.Application.ManageBot throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid"); } - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } - public ITradingBot CreateBacktestTradingBot(TradingBotConfig config) + public async Task CreateBacktestTradingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -418,22 +417,15 @@ namespace Managing.Application.ManageBot } config.IsForBacktest = true; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } - public ITradingBot CreateScalpingBot(TradingBotConfig config) + public async Task CreateScalpingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -450,22 +442,15 @@ namespace Managing.Application.ManageBot } config.FlipPosition = false; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } - public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config) + public async Task CreateBacktestScalpingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -483,22 +468,15 @@ namespace Managing.Application.ManageBot config.IsForBacktest = true; config.FlipPosition = false; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } - public ITradingBot CreateFlippingBot(TradingBotConfig config) + public async Task CreateFlippingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -515,22 +493,15 @@ namespace Managing.Application.ManageBot } config.FlipPosition = true; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } - public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config) + public async Task CreateBacktestFlippingBot(TradingBotConfig config) { // Ensure the scenario is properly loaded from database if needed if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) { - var scenario = _tradingService.GetScenarioByName(config.ScenarioName); + var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName); if (scenario != null) { config.Scenario = scenario; @@ -548,14 +519,7 @@ namespace Managing.Application.ManageBot config.IsForBacktest = true; config.FlipPosition = true; - return new TradingBot( - _exchangeService, - _tradingBotLogger, - _tradingService, - _accountService, - _messengerService, - this, - config); + return new TradingBot(_tradingBotLogger, _scopeFactory, config); } } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/GetAllAgentsCommandHandler.cs b/src/Managing.Application/ManageBot/GetAllAgentsCommandHandler.cs index e0f4392..72ba073 100644 --- a/src/Managing.Application/ManageBot/GetAllAgentsCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetAllAgentsCommandHandler.cs @@ -21,7 +21,7 @@ namespace Managing.Application.ManageBot _accountService = accountService; } - public async Task>> Handle(GetAllAgentsCommand request, + public Task>> Handle(GetAllAgentsCommand request, CancellationToken cancellationToken) { var result = new Dictionary>(); @@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot result[bot.User].Add(bot); } - return result; + return Task.FromResult(result); } /// diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs index 9f344f9..832f08a 100644 --- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -18,9 +18,9 @@ public class LoadBackupBotCommandHandler : IRequestHandler Handle(LoadBackupBotCommand request, CancellationToken cancellationToken) + public async Task Handle(LoadBackupBotCommand request, CancellationToken cancellationToken) { - var backupBots = _botService.GetSavedBots().ToList(); + var backupBots = (await _botService.GetSavedBotsAsync()).ToList(); _logger.LogInformation("Loading {Count} backup bots.", backupBots.Count); var result = new Dictionary(); @@ -42,7 +42,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler /// The trading bot instance /// Context information for the log - private async Task LogBotConfigurationAsync(ITradingBot bot, string context) + private void LogBotConfigurationAsync(ITradingBot bot, string context) { try { diff --git a/src/Managing.Application/Managing.Application.csproj b/src/Managing.Application/Managing.Application.csproj index 34f6de5..13746e6 100644 --- a/src/Managing.Application/Managing.Application.csproj +++ b/src/Managing.Application/Managing.Application.csproj @@ -1,35 +1,35 @@ - - net8.0 - enable - AnyCPU;x64 - + + net8.0 + enable + AnyCPU;x64 + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + diff --git a/src/Managing.Application/MoneyManagements/MoneyManagementService.cs b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs index 4a10942..ed551b6 100644 --- a/src/Managing.Application/MoneyManagements/MoneyManagementService.cs +++ b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs @@ -38,9 +38,28 @@ public class MoneyManagementService : IMoneyManagementService if (moneyManagement == null) { - request.User = user; - await _settingsRepository.InsertMoneyManagement(request); - return request; + // Convert MoneyManagement to LightMoneyManagement for insertion + var lightRequest = new LightMoneyManagement + { + Name = request.Name, + Timeframe = request.Timeframe, + StopLoss = request.StopLoss, + TakeProfit = request.TakeProfit, + Leverage = request.Leverage + }; + + await _settingsRepository.InsertMoneyManagement(lightRequest, user); + + // Return the created money management with user + return new MoneyManagement + { + Name = lightRequest.Name, + Timeframe = lightRequest.Timeframe, + StopLoss = lightRequest.StopLoss, + TakeProfit = lightRequest.TakeProfit, + Leverage = lightRequest.Leverage, + User = user + }; } else { @@ -51,14 +70,28 @@ public class MoneyManagementService : IMoneyManagementService "You do not have permission to update this money management strategy."); } - moneyManagement.StopLoss = request.StopLoss; - moneyManagement.TakeProfit = request.TakeProfit; - moneyManagement.Leverage = request.Leverage; - moneyManagement.Timeframe = request.Timeframe; - moneyManagement.User = user; + // Convert to LightMoneyManagement for update + var lightRequest = new LightMoneyManagement + { + Name = request.Name, + Timeframe = request.Timeframe, + StopLoss = request.StopLoss, + TakeProfit = request.TakeProfit, + Leverage = request.Leverage + }; - _settingsRepository.UpdateMoneyManagement(moneyManagement); - return moneyManagement; + await _settingsRepository.UpdateMoneyManagementAsync(lightRequest, user); + + // Return updated money management + return new MoneyManagement + { + Name = lightRequest.Name, + Timeframe = lightRequest.Timeframe, + StopLoss = lightRequest.StopLoss, + TakeProfit = lightRequest.TakeProfit, + Leverage = lightRequest.Leverage, + User = user + }; } } @@ -67,18 +100,18 @@ public class MoneyManagementService : IMoneyManagementService return await _settingsRepository.GetMoneyManagement(name); } - public IEnumerable GetMoneyMangements(User user) + public async Task> GetMoneyMangements(User user) { try { // Try to use user-specific repository method first - return _settingsRepository.GetMoneyManagementsByUser(user); + return await _settingsRepository.GetMoneyManagementsByUserAsync(user); } catch { // Fall back to filtering if user-specific endpoint is not implemented - return _settingsRepository.GetMoneyManagements() - .Where(mm => mm.User?.Name == user.Name); + var allMoneyManagements = await _settingsRepository.GetMoneyManagementsAsync(); + return allMoneyManagements.Where(mm => mm.User?.Name == user.Name); } } @@ -106,19 +139,19 @@ public class MoneyManagementService : IMoneyManagementService return moneyManagement; } - public bool DeleteMoneyManagement(User user, string name) + public async Task DeleteMoneyManagement(User user, string name) { try { try { // Try to use user-specific repository method first - _settingsRepository.DeleteMoneyManagementByUser(user, name); + await _settingsRepository.DeleteMoneyManagementByUserAsync(user, name); } catch { // Fall back to verifying user ownership before deletion - var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result; + var moneyManagement = await _settingsRepository.GetMoneyManagement(name); if (moneyManagement != null && moneyManagement.User?.Name != user.Name) { @@ -126,7 +159,7 @@ public class MoneyManagementService : IMoneyManagementService "You do not have permission to delete this money management strategy."); } - _settingsRepository.DeleteMoneyManagement(name); + await _settingsRepository.DeleteMoneyManagementAsync(name); } return true; @@ -138,14 +171,14 @@ public class MoneyManagementService : IMoneyManagementService } } - public bool DeleteMoneyManagements(User user) + public async Task DeleteMoneyManagements(User user) { try { try { // Try to use user-specific repository method first - _settingsRepository.DeleteMoneyManagementsByUser(user); + await _settingsRepository.DeleteMoneyManagementsByUserAsync(user); } catch { diff --git a/src/Managing.Application/Scenarios/ScenarioService.cs b/src/Managing.Application/Scenarios/ScenarioService.cs index daa1ee1..417c247 100644 --- a/src/Managing.Application/Scenarios/ScenarioService.cs +++ b/src/Managing.Application/Scenarios/ScenarioService.cs @@ -4,7 +4,6 @@ using Managing.Domain.Scenarios; using Managing.Domain.Strategies; using Managing.Domain.Users; using Microsoft.Extensions.Logging; -using MongoDB.Driver; using static Managing.Common.Enums; namespace Managing.Application.Scenarios @@ -20,20 +19,20 @@ namespace Managing.Application.Scenarios _tradingService = tradingService; } - public Scenario CreateScenario(string name, List strategies, int? loopbackPeriod = 1) + public async Task CreateScenario(string name, List strategies, int? loopbackPeriod = 1) { var scenario = new Scenario(name, loopbackPeriod); foreach (var strategy in strategies) { - scenario.AddIndicator(_tradingService.GetStrategyByName(strategy)); + scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy)); } try { - _tradingService.InsertScenario(scenario); + await _tradingService.InsertScenarioAsync(scenario); } - catch (MongoCommandException ex) + catch (Exception ex) { _logger.LogError(ex.Message); throw new Exception("Cannot create scenario"); @@ -42,7 +41,7 @@ namespace Managing.Application.Scenarios return scenario; } - public Indicator CreateStrategy( + public async Task CreateStrategy( IndicatorType type, string name, int? period = null, @@ -65,30 +64,25 @@ namespace Managing.Application.Scenarios stochPeriods, smoothPeriods, cyclePeriods); - _tradingService.InsertStrategy(strategy); + await _tradingService.InsertStrategyAsync(strategy); return strategy; } - public IEnumerable GetScenarios() + public async Task> GetScenariosAsync() { - return _tradingService.GetScenarios(); + return await _tradingService.GetScenariosAsync(); } - public Scenario GetScenario(string name) + public async Task> GetIndicatorsAsync() { - return _tradingService.GetScenarioByName(name); + return await _tradingService.GetStrategiesAsync(); } - public IEnumerable GetIndicators() - { - return _tradingService.GetStrategies(); - } - - public bool DeleteScenario(string name) + public async Task DeleteScenarioAsync(string name) { try { - _tradingService.DeleteScenario(name); + await _tradingService.DeleteScenarioAsync(name); return true; } catch (Exception ex) @@ -98,61 +92,19 @@ namespace Managing.Application.Scenarios } } - public bool DeleteStrategy(string name) + public async Task UpdateScenario(string name, List strategies, int? loopbackPeriod) { try { - _tradingService.DeleteStrategy(name); - return true; - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - return false; - } - } - - public bool DeleteStrategies() - { - try - { - _tradingService.DeleteStrategies(); - } - catch (Exception) - { - return false; - } - - return true; - } - - public bool DeleteScenarios() - { - try - { - _tradingService.DeleteScenarios(); - } - catch (Exception) - { - return false; - } - - return true; - } - - public bool UpdateScenario(string name, List strategies, int? loopbackPeriod) - { - try - { - var scenario = _tradingService.GetScenarioByName(name); + var scenario = await _tradingService.GetScenarioByNameAsync(name); scenario.Indicators.Clear(); foreach (var strategy in strategies) { - scenario.AddIndicator(_tradingService.GetStrategyByName(strategy)); + scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy)); } scenario.LoopbackPeriod = loopbackPeriod ?? 1; - _tradingService.UpdateScenario(scenario); + await _tradingService.UpdateScenarioAsync(scenario); return true; } catch (Exception e) @@ -162,13 +114,13 @@ namespace Managing.Application.Scenarios } } - public bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, + public async Task UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods) { try { - var strategy = _tradingService.GetStrategyByName(name); + var strategy = await _tradingService.GetStrategyByNameAsync(name); strategy.Type = indicatorType; strategy.Period = period; strategy.FastPeriods = fastPeriods; @@ -178,7 +130,7 @@ namespace Managing.Application.Scenarios strategy.StochPeriods = stochPeriods; strategy.SmoothPeriods = smoothPeriods; strategy.CyclePeriods = cyclePeriods; - _tradingService.UpdateStrategy(strategy); + await _tradingService.UpdateStrategyAsync(strategy); return true; } catch (Exception e) @@ -188,15 +140,13 @@ namespace Managing.Application.Scenarios } } - // User-specific methods implementation - - public IEnumerable GetScenariosByUser(User user) + public async Task> GetScenariosByUserAsync(User user) { - var scenarios = _tradingService.GetScenarios(); + var scenarios = await _tradingService.GetScenariosAsync(); return scenarios.Where(s => s.User?.Name == user.Name); } - public Scenario CreateScenarioForUser(User user, string name, List strategies, int? loopbackPeriod = 1) + public async Task CreateScenarioForUser(User user, string name, List strategies, int? loopbackPeriod = 1) { var scenario = new Scenario(name, loopbackPeriod ?? 1) { @@ -205,82 +155,104 @@ namespace Managing.Application.Scenarios foreach (var strategyName in strategies) { - var strategy = _tradingService.GetStrategyByName(strategyName); + var strategy = await _tradingService.GetStrategyByNameAsync(strategyName); if (strategy != null && strategy.User?.Name == user.Name) { scenario.AddIndicator(strategy); } } - _tradingService.InsertScenario(scenario); + await _tradingService.InsertScenarioAsync(scenario); return scenario; } - public IEnumerable GetIndicatorsByUser(User user) + public async Task> GetIndicatorsByUserAsync(User user) { - var strategies = _tradingService.GetStrategies(); - return strategies.Where(s => s.User?.Name == user.Name); + var indicators = await GetIndicatorsAsync(); + return indicators.Where(s => s.User?.Name == user.Name); } - public bool DeleteIndicatorByUser(User user, string name) + public async Task DeleteIndicatorByUser(User user, string name) { - var strategy = _tradingService.GetStrategyByName(name); + var strategy = await _tradingService.GetStrategyByNameAsync(name); if (strategy != null && strategy.User?.Name == user.Name) { - _tradingService.DeleteStrategy(strategy.Name); + await _tradingService.DeleteStrategyAsync(strategy.Name); return true; } return false; } - public bool DeleteScenarioByUser(User user, string name) + public async Task DeleteScenarioByUser(User user, string name) { - var scenario = _tradingService.GetScenarioByName(name); - if (scenario != null && scenario.User?.Name == user.Name) + try { - _tradingService.DeleteScenario(scenario.Name); + var scenarios = await GetScenariosByUserAsync(user); + foreach (var scenario in scenarios.Where(s => s.Name == name)) + { + await _tradingService.DeleteScenarioAsync(scenario.Name); + } return true; } - - return false; + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } } - public Scenario GetScenarioByUser(User user, string name) + public async Task 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 GetScenarioByUser(User user, string name) + { + var scenario = await _tradingService.GetScenarioByNameAsync(name); return scenario != null && scenario.User?.Name == user.Name ? scenario : null; } - public Indicator CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null, + public async Task CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null, int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null, double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null, int? cyclePeriods = null) { // Create a new strategy using the existing implementation - var strategy = CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods, + var strategy = await CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods); // Set the user strategy.User = user; // Update the strategy to save the user property - _tradingService.UpdateStrategy(strategy); + await _tradingService.UpdateStrategyAsync(strategy); return strategy; } - public bool DeleteStrategiesByUser(User user) + public async Task DeleteStrategiesByUser(User user) { try { - var strategies = GetIndicatorsByUser(user); - + var strategies = await GetIndicatorsByUserAsync(user); foreach (var strategy in strategies) { - _tradingService.DeleteStrategy(strategy.Name); + await _tradingService.DeleteStrategyAsync(strategy.Name); } - return true; } catch (Exception ex) @@ -290,29 +262,9 @@ namespace Managing.Application.Scenarios } } - public bool DeleteScenariosByUser(User user) + public async Task UpdateScenarioByUser(User user, string name, List strategies, int? loopbackPeriod) { - try - { - var scenarios = GetScenariosByUser(user); - - foreach (var scenario in scenarios) - { - _tradingService.DeleteScenario(scenario.Name); - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - return false; - } - } - - public bool UpdateScenarioByUser(User user, string name, List strategies, int? loopbackPeriod) - { - var scenario = _tradingService.GetScenarioByName(name); + var scenario = await _tradingService.GetScenarioByNameAsync(name); if (scenario == null || scenario.User?.Name != user.Name) { return false; @@ -323,29 +275,29 @@ namespace Managing.Application.Scenarios foreach (var strategyName in strategies) { - var strategy = _tradingService.GetStrategyByName(strategyName); + var strategy = await _tradingService.GetStrategyByNameAsync(strategyName); if (strategy != null && strategy.User?.Name == user.Name) { scenario.AddIndicator(strategy); } } - _tradingService.UpdateScenario(scenario); + await _tradingService.UpdateScenarioAsync(scenario); return true; } - public bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, + public async Task UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods) { - var strategy = _tradingService.GetStrategyByName(name); + var strategy = await _tradingService.GetStrategyByNameAsync(name); if (strategy == null || strategy.User?.Name != user.Name) { return false; } // Use the existing update strategy logic - var result = UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods, + var result = await UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods, signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods); return result; diff --git a/src/Managing.Application/Shared/MessengerService.cs b/src/Managing.Application/Shared/MessengerService.cs index 89f28da..cb6ce3d 100644 --- a/src/Managing.Application/Shared/MessengerService.cs +++ b/src/Managing.Application/Shared/MessengerService.cs @@ -127,7 +127,6 @@ public class MessengerService : IMessengerService // If user is provided, also send to webhook if (user != null) { - user = _userService.GetUser(user.Name); await _webhookService.SendTradeNotification(user, message, isBadBehavior); } } diff --git a/src/Managing.Application/Shared/SettingsService.cs b/src/Managing.Application/Shared/SettingsService.cs index 881ba0a..d17fa81 100644 --- a/src/Managing.Application/Shared/SettingsService.cs +++ b/src/Managing.Application/Shared/SettingsService.cs @@ -32,18 +32,7 @@ public class SettingsService : ISettingsService throw new Exception("Cannot delete all backtests"); } - if (!_scenarioService.DeleteScenarios()) - { - throw new Exception("Cannot delete scenarios"); - } - - if (!_scenarioService.DeleteStrategies()) - { - throw new Exception("Cannot delete all strategies"); - } - - - if (!SetupSettings()) + if (!await SetupSettings()) { throw new Exception("Cannot setup settings"); } @@ -51,7 +40,7 @@ public class SettingsService : ISettingsService return await Task.FromResult(true); } - public bool SetupSettings() + public async Task SetupSettings() { try { @@ -59,7 +48,7 @@ public class SettingsService : ISettingsService // SetupMoneyManagementsSeed(Timeframe.FifteenMinutes); // SetupMoneyManagementsSeed(Timeframe.OneHour); // SetupMoneyManagementsSeed(Timeframe.OneDay); - SetupScenariosSeed(); + await SetupScenariosSeed(); } catch (Exception ex) { @@ -85,107 +74,107 @@ public class SettingsService : ISettingsService // await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement); // } - private void SetupScenariosSeed() + private async Task SetupScenariosSeed() { - SetupMacd(); - SetupRsiDiv(); - SetupRsiDivConfirm(); - SetupSuperTrend(); - SetupChandelierExit(); - SetupStochRsiTrend(); - SetupStochSTCTrend(); - SetupEmaTrend(); - SetupEmaCross(); + await SetupMacd(); + await SetupRsiDiv(); + await SetupRsiDivConfirm(); + await SetupSuperTrend(); + await SetupChandelierExit(); + await SetupStochRsiTrend(); + await SetupStochSTCTrend(); + await SetupEmaTrend(); + await SetupEmaCross(); } - private void SetupStochSTCTrend() + private async Task SetupStochSTCTrend() { var name = "STCTrend"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.Stc, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.Stc, name, fastPeriods: 23, slowPeriods: 50, cyclePeriods: 10); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupMacd() + private async Task SetupMacd() { var name = "MacdCross"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.MacdCross, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.MacdCross, name, fastPeriods: 12, slowPeriods: 26, signalPeriods: 9); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupRsiDiv() + private async Task SetupRsiDiv() { var name = "RsiDiv6"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergence, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergence, name, period: 6); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupRsiDivConfirm() + private async Task SetupRsiDivConfirm() { var name = "RsiDivConfirm6"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm, name, period: 6); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupSuperTrend() + private async Task SetupSuperTrend() { var name = "SuperTrend"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.SuperTrend, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.SuperTrend, name, period: 10, multiplier: 3); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupChandelierExit() + private async Task SetupChandelierExit() { var name = "ChandelierExit"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.ChandelierExit, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.ChandelierExit, name, period: 22, multiplier: 3); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupStochRsiTrend() + private async Task SetupStochRsiTrend() { var name = "StochRsiTrend"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend, name, period: 14, stochPeriods: 14, signalPeriods: 3, smoothPeriods: 1); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupEmaTrend() + private async Task SetupEmaTrend() { var name = "Ema200Trend"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaTrend, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaTrend, name, period: 200); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } - private void SetupEmaCross() + private async Task SetupEmaCross() { var name = "Ema200Cross"; - var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaCross, + var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaCross, name, period: 200); - _scenarioService.CreateScenario(name, new List { strategy.Name }); + await _scenarioService.CreateScenario(name, new List { strategy.Name }); } public async Task CreateDefaultConfiguration(User user) @@ -212,7 +201,7 @@ public class SettingsService : ISettingsService await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement); // Create default Strategy (StcTrend) - var defaultStrategy = _scenarioService.CreateIndicatorForUser( + var defaultStrategy = await _scenarioService.CreateIndicatorForUser( user, IndicatorType.Stc, "Stc", @@ -226,7 +215,7 @@ public class SettingsService : ISettingsService // Create default Scenario containing the strategy var strategyNames = new List { defaultStrategy.Name }; - var defaultScenario = _scenarioService.CreateScenarioForUser( + var defaultScenario = await _scenarioService.CreateScenarioForUser( user, "STC Scenario", strategyNames diff --git a/src/Managing.Application/Synth/SynthPredictionService.cs b/src/Managing.Application/Synth/SynthPredictionService.cs index 3e4daf7..3e208a4 100644 --- a/src/Managing.Application/Synth/SynthPredictionService.cs +++ b/src/Managing.Application/Synth/SynthPredictionService.cs @@ -1,9 +1,7 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Bots; -using Managing.Domain.MoneyManagements; using Managing.Domain.Risk; -using Managing.Domain.Strategies; using Managing.Domain.Synth.Models; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -223,7 +221,7 @@ public class SynthPredictionService : ISynthPredictionService /// Whether this is a backtest scenario /// Custom probability thresholds for decision-making. If null, uses default thresholds. /// Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status - public async Task ValidateSignalAsync(Signal signal, decimal currentPrice, + public async Task ValidateSignalAsync(LightSignal signal, decimal currentPrice, TradingBotConfig botConfig, bool isBacktest, Dictionary customThresholds = null) { var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig); @@ -925,7 +923,7 @@ public class SynthPredictionService : ISynthPredictionService /// Estimates liquidation price based on money management settings /// public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, - MoneyManagement moneyManagement) + LightMoneyManagement moneyManagement) { // This is a simplified estimation - in reality, you'd use the actual money management logic var riskPercentage = 0.02m; // Default 2% risk diff --git a/src/Managing.Application/Trading/ClosePositionCommandHandler.cs b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs index dea8347..4d5c1c0 100644 --- a/src/Managing.Application/Trading/ClosePositionCommandHandler.cs +++ b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs @@ -23,7 +23,7 @@ public class ClosePositionCommandHandler( var account = await accountService.GetAccount(request.Position.AccountName, false, false); if (request.Position == null) { - _ = exchangeService.CancelOrder(account, request.Position.Ticker).Result; + _ = await exchangeService.CancelOrder(account, request.Position.Ticker); return request.Position; } @@ -31,7 +31,7 @@ public class ClosePositionCommandHandler( var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading ? request.ExecutionPrice.GetValueOrDefault() - : exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow); + : await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow); // Check if position still open if (!request.IsForBacktest) @@ -46,7 +46,7 @@ public class ClosePositionCommandHandler( request.Position.ProfitAndLoss = TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice, request.Position.Open.Leverage); - tradingService.UpdatePosition(request.Position); + await tradingService.UpdatePositionAsync(request.Position); return request.Position; } } @@ -67,7 +67,7 @@ public class ClosePositionCommandHandler( request.Position.Open.Leverage); if (!request.IsForBacktest) - tradingService.UpdatePosition(request.Position); + await tradingService.UpdatePositionAsync(request.Position); } return request.Position; diff --git a/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs b/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs deleted file mode 100644 index 4b8b6bd..0000000 --- a/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Managing.Common; -using Managing.Domain.Trades; -using MediatR; - -namespace Managing.Application.Trading.Commands -{ - public class GetPositionsCommand : IRequest> - { - public GetPositionsCommand(Enums.PositionInitiator initiator) - { - Initiator = initiator; - } - - public Enums.PositionInitiator Initiator { get; internal set; } - } -} diff --git a/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs index f5e0ea1..1e418e4 100644 --- a/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs +++ b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs @@ -1,5 +1,4 @@ -using Managing.Domain.MoneyManagements; -using Managing.Domain.Trades; +using Managing.Domain.Trades; using Managing.Domain.Users; using MediatR; using static Managing.Common.Enums; @@ -10,7 +9,7 @@ namespace Managing.Application.Trading.Commands { public OpenPositionRequest( string accountName, - MoneyManagement moneyManagement, + LightMoneyManagement moneyManagement, TradeDirection direction, Ticker ticker, PositionInitiator initiator, @@ -43,7 +42,7 @@ namespace Managing.Application.Trading.Commands public string SignalIdentifier { get; set; } public string AccountName { get; } - public MoneyManagement MoneyManagement { get; } + public LightMoneyManagement MoneyManagement { get; } public TradeDirection Direction { get; } public Ticker Ticker { get; } public bool IsForPaperTrading { get; } diff --git a/src/Managing.Application/Trading/GetPositionsCommandHandler.cs b/src/Managing.Application/Trading/GetPositionsCommandHandler.cs deleted file mode 100644 index e8c9521..0000000 --- a/src/Managing.Application/Trading/GetPositionsCommandHandler.cs +++ /dev/null @@ -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> - { - private readonly ITradingService _tradingService; - - public GetPositionsCommandHandler(ITradingService tradingService) - { - _tradingService = tradingService; - } - - public Task> Handle(GetPositionsCommand request, CancellationToken cancellationToken) - { - var positions = _tradingService.GetPositions(request.Initiator); - return Task.FromResult(positions.ToList()); - } - } -} diff --git a/src/Managing.Application/Trading/GetTradeCommandHandler.cs b/src/Managing.Application/Trading/GetTradeCommandHandler.cs index 9f81d62..88740fe 100644 --- a/src/Managing.Application/Trading/GetTradeCommandHandler.cs +++ b/src/Managing.Application/Trading/GetTradeCommandHandler.cs @@ -15,9 +15,9 @@ public class GetTradeCommandHandler : IRequestHandler _accountService = accountService; } - public Task Handle(GetTradeCommand request, CancellationToken cancellationToken) + public async Task Handle(GetTradeCommand request, CancellationToken cancellationToken) { - var account = _accountService.GetAccount(request.AccountName, true, false).Result; - return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker); + var account = await _accountService.GetAccount(request.AccountName, true, false); + return await _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker); } } diff --git a/src/Managing.Application/Trading/GetTradesCommandHandler.cs b/src/Managing.Application/Trading/GetTradesCommandHandler.cs index f44a189..aade314 100644 --- a/src/Managing.Application/Trading/GetTradesCommandHandler.cs +++ b/src/Managing.Application/Trading/GetTradesCommandHandler.cs @@ -16,10 +16,10 @@ namespace Managing.Application.Trading _accountService = accountService; } - public Task> Handle(GetTradesCommand request, CancellationToken cancellationToken) + public async Task> Handle(GetTradesCommand request, CancellationToken cancellationToken) { - var account = _accountService.GetAccount(request.AccountName, true, false).Result; - return _exchangeService.GetTrades(account, request.Ticker); + var account = await _accountService.GetAccount(request.AccountName, true, false); + return await _exchangeService.GetTrades(account, request.Ticker); } } } diff --git a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs index 08f92d7..69b831a 100644 --- a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs +++ b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs @@ -49,12 +49,12 @@ namespace Managing.Application.Trading var price = request.IsForPaperTrading && request.Price.HasValue ? request.Price.Value - : exchangeService.GetPrice(account, request.Ticker, DateTime.Now); + : await exchangeService.GetPrice(account, request.Ticker, DateTime.Now); var quantity = balanceToRisk / price; var openPrice = request.IsForPaperTrading || request.Price.HasValue ? request.Price.Value - : exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction); + : await exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction); // Determine SL/TP Prices var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement); @@ -107,22 +107,12 @@ namespace Managing.Application.Trading if (!request.IsForPaperTrading) { - tradingService.InsertPosition(position); + await tradingService.InsertPositionAsync(position); } return position; } - private static TradeStatus GetExpectedStatus(OpenPositionRequest request) - { - if (request.IsForPaperTrading) - { - return TradeStatus.Filled; - } - - return TradeStatus.Requested; - } - private static bool IsOpenTradeHandled(TradeStatus tradeStatus, TradingExchanges exchange) { return tradeStatus == TradeStatus.Filled diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 55474bb..e528e51 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -51,85 +51,70 @@ public class TradingService : ITradingService _synthPredictionService = synthPredictionService; } - public void DeleteScenario(string name) + public async Task DeleteScenarioAsync(string name) { - _tradingRepository.DeleteScenario(name); + await _tradingRepository.DeleteScenarioAsync(name); } - public void DeleteScenarios() + public async Task DeleteStrategyAsync(string name) { - _tradingRepository.DeleteScenarios(); + await _tradingRepository.DeleteIndicatorAsync(name); } - public void DeleteStrategies() + public async Task GetPositionByIdentifierAsync(string identifier) { - _tradingRepository.DeleteIndicators(); + return await _tradingRepository.GetPositionByIdentifierAsync(identifier); } - public void DeleteStrategy(string name) + public async Task> GetPositionsAsync(PositionInitiator positionInitiator) { - _tradingRepository.DeleteIndicator(name); + return await _tradingRepository.GetPositionsAsync(positionInitiator); } - public Position GetPositionByIdentifier(string identifier) + public async Task> GetPositionsByStatusAsync(PositionStatus postionStatus) { - return _tradingRepository.GetPositionByIdentifier(identifier); - } - - public IEnumerable GetPositions(PositionInitiator positionInitiator) - { - return _tradingRepository.GetPositions(positionInitiator); - } - - public IEnumerable GetPositionsByStatus(PositionStatus postionStatus) - { - return _tradingRepository.GetPositionsByStatus(postionStatus); + return await _tradingRepository.GetPositionsByStatusAsync(postionStatus); } - public Scenario GetScenarioByName(string scenario) + public async Task GetScenarioByNameAsync(string scenario) { - return _tradingRepository.GetScenarioByName(scenario); + return await _tradingRepository.GetScenarioByNameAsync(scenario); } - public IEnumerable GetScenarios() + public async Task> GetScenariosAsync() { - return _tradingRepository.GetScenarios(); + return await _tradingRepository.GetScenariosAsync(); } - public IEnumerable GetStrategies() + public async Task> GetStrategiesAsync() { - return _tradingRepository.GetIndicators(); + return await _tradingRepository.GetStrategiesAsync(); } - public Indicator GetStrategyByName(string strategy) + public async Task GetStrategyByNameAsync(string strategy) { - return _tradingRepository.GetStrategyByName(strategy); + return await _tradingRepository.GetStrategyByNameAsync(strategy); } - public void InsertPosition(Position position) + public async Task InsertPositionAsync(Position position) { - _tradingRepository.InsertPosition(position); + await _tradingRepository.InsertPositionAsync(position); } - public void InsertScenario(Scenario scenario) + public async Task InsertScenarioAsync(Scenario scenario) { - _tradingRepository.InsertScenario(scenario); + await _tradingRepository.InsertScenarioAsync(scenario); } - public void InsertSignal(Signal signal) + public async Task InsertStrategyAsync(Indicator indicator) { - _tradingRepository.InsertSignal(signal); - } - - public void InsertStrategy(Indicator indicator) - { - _tradingRepository.InsertStrategy(indicator); + await _tradingRepository.InsertStrategyAsync(indicator); } public async Task ManagePosition(Account account, Position position) { - var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow); + var lastPrice = await _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow); var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker); var orders = await _exchangeService.GetOpenOrders(account, position.Ticker); @@ -184,54 +169,19 @@ public class TradingService : ITradingService return position; } - public void UpdateFee(TradingExchanges exchange) - { - var lastFee = _tradingRepository.GetFee(exchange); - var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); - if (lastFee != null) - { - if (DateTime.UtcNow.AddHours(-6) >= lastFee.LastUpdate) - { - lastFee.Cost = _exchangeService.GetFee(account); - lastFee.LastUpdate = DateTime.UtcNow; - _tradingRepository.UpdateFee(lastFee); - } - } - else - { - lastFee = new Fee - { - Cost = _exchangeService.GetFee(account), - Exchange = exchange, - LastUpdate = DateTime.UtcNow - }; - _tradingRepository.InsertFee(lastFee); - } + + public async Task UpdatePositionAsync(Position position) + { + await _tradingRepository.UpdatePositionAsync(position); } - public decimal GetFee(Account account, bool isForPaperTrading = false) - { - if (isForPaperTrading && account.Exchange != TradingExchanges.Evm) - { - return 0.000665M; - } - - return _cacheService.GetOrSave($"Fee-{account.Exchange}", - () => { return (decimal)_tradingRepository.GetFee(TradingExchanges.Evm)?.Cost; }, TimeSpan.FromHours(2)); - } - - public void UpdatePosition(Position position) - { - _tradingRepository.UpdatePosition(position); - } - - public IEnumerable GetPositions() + public async Task> GetPositionsAsync() { var positions = new List(); - positions.AddRange(GetPositionsByStatus(PositionStatus.New)); - positions.AddRange(GetPositionsByStatus(PositionStatus.Filled)); - positions.AddRange(GetPositionsByStatus(PositionStatus.PartiallyFilled)); + positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.New)); + positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.Filled)); + positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.PartiallyFilled)); return positions; } @@ -239,7 +189,7 @@ public class TradingService : ITradingService public async Task WatchTrader() { var availableTickers = new List { Ticker.BTC, Ticker.ETH, Ticker.UNI, Ticker.LINK }; - var watchAccount = GetTradersWatch(); + var watchAccount = await GetTradersWatch(); var key = $"AccountsQuantityInPosition"; var aqip = _cacheService.GetValue>(key); @@ -264,10 +214,11 @@ public class TradingService : ITradingService _cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10)); } - public IEnumerable GetTradersWatch() + public async Task> GetTradersWatch() { - var watchAccount = _statisticRepository.GetBestTraders(); - var customWatchAccount = _accountService.GetAccounts(true, false).Where(a => a.Type == AccountType.Watch) + var watchAccount = await _statisticRepository.GetBestTradersAsync(); + var customWatchAccount = (await _accountService.GetAccountsAsync(true, false)) + .Where(a => a.Type == AccountType.Watch) .ToList().MapToTraders(); watchAccount.AddRange(customWatchAccount.Where(a => !watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase)))); @@ -279,14 +230,14 @@ public class TradingService : ITradingService var fundingRates = _exchangeService.GetFundingRates(); } - public void UpdateScenario(Scenario scenario) + public async Task UpdateScenarioAsync(Scenario scenario) { - _tradingRepository.UpdateScenario(scenario); + await _tradingRepository.UpdateScenarioAsync(scenario); } - public void UpdateStrategy(Indicator indicator) + public async Task UpdateStrategyAsync(Indicator indicator) { - _tradingRepository.UpdateStrategy(indicator); + await _tradingRepository.UpdateStrategyAsync(indicator); } public async Task> GetBrokerPositions(Account account) @@ -407,7 +358,7 @@ public class TradingService : ITradingService } // Synth API integration methods - public async Task ValidateSynthSignalAsync(Signal signal, decimal currentPrice, + public async Task ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice, TradingBotConfig botConfig, bool isBacktest) { return await _synthPredictionService.ValidateSignalAsync(signal, currentPrice, botConfig, isBacktest); @@ -433,12 +384,12 @@ public class TradingService : ITradingService /// The scenario containing indicators. /// The candles to calculate indicators for. /// A dictionary of indicator types to their calculated values. - public async Task> CalculateIndicatorsValuesAsync( - Scenario scenario, + public Dictionary CalculateIndicatorsValuesAsync( + Scenario scenario, List candles) { var indicatorsValues = new Dictionary(); - + if (scenario?.Indicators == null || scenario.Indicators.Count == 0) { return indicatorsValues; @@ -459,13 +410,13 @@ public class TradingService : ITradingService // Build the indicator using ScenarioHelpers var builtIndicator = ScenarioHelpers.BuildIndicator(indicator, 10000); builtIndicator.Candles = fixedCandles; - + indicatorsValues[indicator.Type] = builtIndicator.GetIndicatorValues(); } catch (Exception ex) { // Log the error but continue with other indicators - _logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}", + _logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}", indicator.Name, ex.Message); } } diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 6f7a049..a1a6290 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -14,6 +14,7 @@ public class UserService : IUserService private readonly IUserRepository _userRepository; private readonly IAccountService _accountService; private readonly ILogger _logger; + private readonly ICacheService _cacheService; private string[] authorizedAddresses = [ @@ -32,12 +33,15 @@ public class UserService : IUserService public UserService( IEvmManager evmManager, IUserRepository userRepository, - IAccountService accountService, ILogger logger) + IAccountService accountService, + ILogger logger, + ICacheService cacheService) { _evmManager = evmManager; _userRepository = userRepository; _accountService = accountService; _logger = logger; + _cacheService = cacheService; } public async Task Authenticate(string name, string address, string message, string signature) @@ -116,16 +120,27 @@ public class UserService : IUserService return user; } - public User GetUser(string name) - { - return _userRepository.GetUserByNameAsync(name).Result; - } - public async Task GetUserByAddressAsync(string address) { + var cacheKey = $"user-by-address-{address}"; + + // Check cache first + var cachedUser = _cacheService.GetValue(cacheKey); + if (cachedUser != null) + { + return cachedUser; + } + + // Cache miss - fetch from database var account = await _accountService.GetAccountByKey(address, true, false); var user = await _userRepository.GetUserByNameAsync(account.User.Name); - user.Accounts = _accountService.GetAccountsByUser(user).ToList(); + + // Use proper async version to avoid DbContext concurrency issues + user.Accounts = (await _accountService.GetAccountsByUserAsync(user)).ToList(); + + // Save to cache for 10 minutes (JWT middleware calls this on every request) + _cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(10)); + return user; } @@ -148,7 +163,7 @@ public class UserService : IUserService public async Task UpdateAvatarUrl(User user, string avatarUrl) { // Validate URL format and image extension - if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out Uri? uriResult) || + if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out Uri? uriResult) || (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps)) { throw new Exception("Invalid URL format"); @@ -182,4 +197,9 @@ public class UserService : IUserService await _userRepository.UpdateUser(user); return user; } + + public async Task GetUser(string name) + { + return await _userRepository.GetUserByNameAsync(name); + } } \ No newline at end of file diff --git a/src/Managing.Application/Workers/BalanceTrackingWorker.cs b/src/Managing.Application/Workers/BalanceTrackingWorker.cs index 91cd714..d5bf03a 100644 --- a/src/Managing.Application/Workers/BalanceTrackingWorker.cs +++ b/src/Managing.Application/Workers/BalanceTrackingWorker.cs @@ -2,7 +2,6 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.ManageBot.Commands; -using Managing.Application.Workers.Abstractions; using Managing.Domain.Statistics; using MediatR; using Microsoft.Extensions.Logging; @@ -19,15 +18,15 @@ public class BalanceTrackingWorker : BaseWorker public BalanceTrackingWorker( ILogger logger, + IServiceProvider serviceProvider, IMediator mediator, IAccountService accountService, - IAgentBalanceRepository agentBalanceRepository, - IWorkerService workerService) + IAgentBalanceRepository agentBalanceRepository) : base( WorkerType.BalanceTracking, logger, TimeSpan.FromHours(1), - workerService) + serviceProvider) { _mediator = mediator; _accountService = accountService; diff --git a/src/Managing.Application/Workers/BotManagerWorker.cs b/src/Managing.Application/Workers/BotManagerWorker.cs index eb2bc4c..c0d5896 100644 --- a/src/Managing.Application/Workers/BotManagerWorker.cs +++ b/src/Managing.Application/Workers/BotManagerWorker.cs @@ -1,5 +1,4 @@ using Managing.Application.ManageBot; -using Managing.Application.Workers.Abstractions; using MediatR; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -8,12 +7,12 @@ namespace Managing.Application.Workers; public class BotManagerWorker( ILogger logger, - IMediator mediadior, - IWorkerService workerService) + IServiceProvider serviceProvider, + IMediator mediadior) : BaseWorker(WorkerType.BotManager, logger, TimeSpan.FromMinutes(5), - workerService) + serviceProvider) { protected override async Task Run(CancellationToken cancellationToken) { diff --git a/src/Managing.Application/Workers/NotifyBundleBacktestWorker.cs b/src/Managing.Application/Workers/NotifyBundleBacktestWorker.cs index b20f500..bf73ab4 100644 --- a/src/Managing.Application/Workers/NotifyBundleBacktestWorker.cs +++ b/src/Managing.Application/Workers/NotifyBundleBacktestWorker.cs @@ -1,9 +1,9 @@ using System.Collections.Concurrent; using Managing.Application.Abstractions.Services; using Managing.Application.Hubs; -using Managing.Application.Workers.Abstractions; using Managing.Domain.Backtests; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -11,18 +11,17 @@ namespace Managing.Application.Workers; public class NotifyBundleBacktestWorker : BaseWorker { - private readonly IBacktester _backtester; + private readonly IServiceProvider _serviceProvider; private readonly IHubContext _hubContext; private readonly ConcurrentDictionary> _sentBacktestIds = new(); public NotifyBundleBacktestWorker( - IBacktester backtester, + IServiceProvider serviceProvider, IHubContext hubContext, - ILogger logger, - IWorkerService workerService) - : base(WorkerType.NotifyBundleBacktest, logger, TimeSpan.FromMinutes(1), workerService) + ILogger logger) + : base(WorkerType.NotifyBundleBacktest, logger, TimeSpan.FromMinutes(1), serviceProvider) { - _backtester = backtester; + _serviceProvider = serviceProvider; _hubContext = hubContext; } @@ -30,8 +29,12 @@ public class NotifyBundleBacktestWorker : BaseWorker { 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(); + // Fetch all running bundle requests - var runningBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Running); + var runningBundles = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Running); foreach (var bundle in runningBundles) { @@ -39,7 +42,7 @@ public class NotifyBundleBacktestWorker : BaseWorker if (string.IsNullOrEmpty(requestId)) continue; // Fetch all backtests for this bundle - var (backtests, _) = _backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100); + var (backtests, _) = backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100); if (!_sentBacktestIds.ContainsKey(requestId)) _sentBacktestIds[requestId] = new HashSet(); diff --git a/src/Managing.Application/Workflows/FlowFactory.cs b/src/Managing.Application/Workflows/FlowFactory.cs deleted file mode 100644 index 786a7ba..0000000 --- a/src/Managing.Application/Workflows/FlowFactory.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Managing.Application.Abstractions; -using Managing.Application.Abstractions.Services; -using Managing.Application.Workflows.Flows.Feeds; -using Managing.Application.Workflows.Flows.Strategies; -using Managing.Application.Workflows.Flows.Trading; -using Managing.Domain.Workflows; -using Managing.Domain.Workflows.Synthetics; -using static Managing.Common.Enums; - -namespace Managing.Application.Workflows; - -public class FlowFactory : IFlowFactory -{ - private readonly IExchangeService _exchangeService; - private readonly ICacheService _cacheService; - private readonly ITradingService _tradingService; - private readonly IAccountService _accountService; - - public FlowFactory(IExchangeService exchangeService, ICacheService cacheService, ITradingService tradingService, - IAccountService accountService) - { - _exchangeService = exchangeService; - _cacheService = cacheService; - _tradingService = tradingService; - _accountService = accountService; - } - - public IFlow BuildFlow(SyntheticFlow request) - { - IFlow flow = request.Type switch - { - FlowType.FeedTicker => new FeedTicker(_exchangeService), - FlowType.RsiDivergence => new RsiDiv(), - FlowType.OpenPosition => new OpenPosition(_exchangeService, _cacheService, _accountService, - _tradingService), - _ => throw new NotImplementedException(), - }; - - flow.Children = new List(); - flow.Parameters = new List(); - - foreach (var parameter in request.Parameters) - { - if (!flow.Parameters.Any(p => p.Name == parameter.Name)) - { - flow.Parameters.Add(new FlowParameter - { - Name = parameter.Name, - Value = parameter.Value - }); - } - } - - return flow; - } -} \ No newline at end of file diff --git a/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs b/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs deleted file mode 100644 index f0087bb..0000000 --- a/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Managing.Application.Abstractions.Services; -using Managing.Domain.Workflows; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Application.Workflows.Flows.Feeds; - -public class FeedTicker : FlowBase -{ - public override List Children { get; set; } - public override List Parameters { get; set; } - public override Guid ParentId { get; } - public override Guid Id { get; } - public override string Output { get; set; } - public override string Name => "Feed Ticker"; - public override FlowType Type => FlowType.FeedTicker; - public override string Description => "This flow will take a feed and output the candles"; - public FeedTickerParameters FeedTickerParameters { get; set; } - public override List AcceptedInputs => new(); - public override List OutputTypes => new() { FlowOutput.Candles }; - private readonly IExchangeService _exchangeService; - - public FeedTicker(IExchangeService exchangeService) - { - _exchangeService = exchangeService; - } - - public async override Task Execute(string input) - { - MapParameters(); - var candles = await _exchangeService.GetCandlesInflux(FeedTickerParameters.Exchange, FeedTickerParameters.Ticker, DateTime.Now.AddDays(-11), FeedTickerParameters.Timeframe); - - Output = JsonConvert.SerializeObject(candles.ToHashSet()); - - if(Children != null && Children.Count > 0) - { - foreach (var child in Children) - { - await child.Execute(Output); - } - } - } - - public override void MapParameters() - { - FeedTickerParameters = new FeedTickerParameters(); - foreach (var param in Parameters) - { - if (param.Name == nameof(FeedTickerParameters.Ticker)) - FeedTickerParameters.Ticker = (Ticker)Enum.Parse(typeof(Ticker), param.Value); - if (param.Name == nameof(FeedTickerParameters.Exchange)) - FeedTickerParameters.Exchange = (TradingExchanges)Enum.Parse(typeof(TradingExchanges), param.Value); - if (param.Name == nameof(FeedTickerParameters.Timeframe)) - FeedTickerParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value); - } - } -} - -public class FeedTickerParameters -{ - public TradingExchanges Exchange { get; set; } - public Ticker Ticker { get; set; } - public Timeframe Timeframe { get; set; } -} diff --git a/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs b/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs deleted file mode 100644 index 662a1aa..0000000 --- a/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Managing.Domain.Candles; -using Managing.Domain.Strategies.Signals; -using Managing.Domain.Workflows; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Application.Workflows.Flows.Strategies; - -public class RsiDiv : FlowBase -{ - public override List Children { get; set; } - public override List Parameters { get; set; } - public override Guid ParentId { get; } - public override Guid Id { get; } - public override string Output { get; set; } - public override string Name => "Rsi Divergence"; - public override string Description => "This flow will take a feed of candle an run the RSI Divergence"; - public override FlowType Type => FlowType.RsiDivergence; - public override List AcceptedInputs => new() { FlowOutput.Candles }; - public override List OutputTypes => new() { FlowOutput.Signal }; - public RsiDivParameters RsiDivParameters { get; set; } - - public async override Task Execute(string input) - { - if (string.IsNullOrEmpty(input)) - { - throw new ArgumentException($"'{nameof(input)}' cannot be null or empty.", nameof(input)); - } - - MapParameters(); - var candles = JsonConvert.DeserializeObject>(input); - - var strategy = new RsiDivergenceIndicator(Name, RsiDivParameters.Period); - strategy.UpdateCandles(candles); - strategy.Run(); - - Output = JsonConvert.SerializeObject(strategy.Signals); - - if (Children != null && Children.Count > 0) - { - foreach (var child in Children) - { - await child.Execute(Output); - } - } - } - - public override void MapParameters() - { - RsiDivParameters = new RsiDivParameters(); - foreach (var param in Parameters) - { - if (param.Name == nameof(RsiDivParameters.Period)) - RsiDivParameters.Period = int.Parse(param.Value); - if (param.Name == nameof(RsiDivParameters.Timeframe)) - RsiDivParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value); - } - } -} - -public class RsiDivParameters -{ - public int Period { get; set; } - public Timeframe Timeframe { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs deleted file mode 100644 index aba5355..0000000 --- a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Managing.Application.Abstractions; -using Managing.Application.Abstractions.Repositories; -using Managing.Application.Abstractions.Services; -using Managing.Application.Trading; -using Managing.Application.Trading.Commands; -using Managing.Domain.Accounts; -using Managing.Domain.Candles; -using Managing.Domain.Strategies; -using Managing.Domain.Trades; -using Managing.Domain.Workflows; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Application.Workflows.Flows.Trading; - -public class OpenPosition : FlowBase -{ - public override List Children { get; set; } - public override List Parameters { get; set; } - public override Guid ParentId { get; } - public override Guid Id { get; } - public override string Output { get; set; } - public override string Name => "Open Position"; - public override FlowType Type => FlowType.OpenPosition; - public override string Description => "This flow will open a position for a given signal"; - public OpenPositionParameters OpenPositionParameters { get; set; } - public override List AcceptedInputs => new() { FlowOutput.Signal, FlowOutput.MoneyManagement }; - public override List OutputTypes => new() { FlowOutput.Position }; - private readonly IExchangeService _exchangeService; - private readonly ICacheService _cacheService; - private readonly IAccountService _accountService; - private readonly ITradingService _tradingService; - private readonly ISettingsRepository _settingsRepository; - private readonly IMessengerService _messengerService; - - private readonly string POSITIONS_KEY = "positions"; - private readonly string ACCOUNT_KEY = "account"; - private readonly string CANDLES_KEY = "candles"; - private readonly string SIGNALS_KEY = "signals"; - private readonly string FEE_KEY = "fee"; - private readonly string WALLET_BALANCES = "wallet-balance"; - private decimal Fee { get; set; } - - - public OpenPosition( - IExchangeService exchangeService, - ICacheService cacheService, - IAccountService accountService, - ITradingService tradingService) - { - _exchangeService = exchangeService; - _cacheService = cacheService; - _accountService = accountService; - _tradingService = tradingService; - } - - public async override Task Execute(string input) - { - MapParameters(); - var signal = JsonConvert.DeserializeObject(input); - var Candles = JsonConvert.DeserializeObject>(_cacheService.GetValue(CANDLES_KEY)); - var Account = JsonConvert.DeserializeObject(_cacheService.GetValue(ACCOUNT_KEY)); - var Positions = JsonConvert.DeserializeObject>(_cacheService.GetValue(POSITIONS_KEY)); - var Signals = JsonConvert.DeserializeObject>(_cacheService.GetValue(POSITIONS_KEY)); - - await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account); - - _cacheService.SaveValue(POSITIONS_KEY, JsonConvert.SerializeObject(Positions)); - _cacheService.SaveValue(SIGNALS_KEY, JsonConvert.SerializeObject(Signals)); - - if (Children != null && Children.Count > 0) - { - foreach (var child in Children) - { - await child.Execute(Output); - } - } - } - - private async Task ExecuteOpenPosition(Signal signal, List positions, HashSet signals, - HashSet candles, Account account) - { - // Check if a position is already open - var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled - && p.SignalIdentifier != signal.Identifier); - - var lastPrice = OpenPositionParameters.IsForBacktest - ? candles.Last().Close - : _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow); - - // If position open - if (openedPosition != null) - { - var previousSignal = signals.First(s => s.Identifier == openedPosition.SignalIdentifier); - - // Check if signal is the opposite side => flip the position - if (openedPosition.OriginDirection == signal.Direction) - { - // An operation is already open for the same direction - //await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction"); - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; - } - else - { - // An operation is already open for the opposite direction - // ==> Flip the position - if (OpenPositionParameters.FlipPosition) - { - //await LogInformation("Try to flip the position because of an opposite direction signal"); - //await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); - positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status = - PositionStatus.Flipped; - await ExecuteOpenPosition(signal, positions, signals, candles, account); - - //await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$"); - } - else - { - //await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped."); - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; - } - } - } - else - { - if (!CanOpenPosition(signal, positions, signals, candles)) - { - //await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; - return; - } - //await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}"); - - try - { - var moneyManagement = - await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName); - var WalletBalances = - JsonConvert.DeserializeObject>( - _cacheService.GetValue(WALLET_BALANCES)); - - var command = new OpenPositionRequest( - OpenPositionParameters.AccountName, - moneyManagement, - signal.Direction, - signal.Ticker, - PositionInitiator.Bot, - signal.Date, - account.User, - 100m, // Default bot trading balance - OpenPositionParameters.IsForBacktest, - lastPrice); - - var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService) - .Handle(command); - - if (position != null) - { - if (position.Open.Status != TradeStatus.Cancelled) - { - position.SignalIdentifier = signal.Identifier; - positions.Add(position); - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = - SignalStatus.PositionOpen; - - if (!OpenPositionParameters.IsForBacktest) - { - await _messengerService.SendPosition(position); - } - - Output = JsonConvert.SerializeObject(position); - //Logger.LogInformation($"Position requested"); - } - else - { - positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = - PositionStatus.Rejected; - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; - } - } - } - catch (Exception ex) - { - signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; - //await LogWarning($"Cannot open trade : {ex.Message}"); - } - } - } - - private bool CanOpenPosition(Signal signal, List positions, HashSet signals, - HashSet candles) - { - if (positions.Count == 0) - return true; - - var lastPosition = positions.LastOrDefault(p => p.IsFinished() - && p.SignalIdentifier != signal.Identifier - && p.ProfitAndLoss.Realized < 0 - && p.OriginDirection == signal.Direction); - - if (lastPosition == null) - return true; - - var tenCandleAgo = candles.TakeLast(10).First(); - var positionSignal = signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier); - - return positionSignal.Date < tenCandleAgo.Date; - } - - - public override void MapParameters() - { - OpenPositionParameters = new OpenPositionParameters(); - foreach (var param in Parameters) - { - if (param.Name == nameof(OpenPositionParameters.AccountName)) - OpenPositionParameters.AccountName = param.Value; - if (param.Name == nameof(OpenPositionParameters.MoneyManagementName)) - OpenPositionParameters.MoneyManagementName = param.Value; - if (param.Name == nameof(OpenPositionParameters.FlipPosition)) - OpenPositionParameters.FlipPosition = bool.Parse(param.Value); - } - } -} - -public class OpenPositionParameters -{ - public string MoneyManagementName { get; set; } - public string AccountName { get; set; } - public bool IsForBacktest { get; set; } - public bool FlipPosition { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Application/Workflows/WorkflowService.cs b/src/Managing.Application/Workflows/WorkflowService.cs deleted file mode 100644 index 8554179..0000000 --- a/src/Managing.Application/Workflows/WorkflowService.cs +++ /dev/null @@ -1,125 +0,0 @@ -using Managing.Application.Abstractions; -using Managing.Application.Abstractions.Repositories; -using Managing.Application.Abstractions.Services; -using Managing.Application.Workflows.Flows.Feeds; -using Managing.Application.Workflows.Flows.Strategies; -using Managing.Application.Workflows.Flows.Trading; -using Managing.Domain.Workflows; -using Managing.Domain.Workflows.Synthetics; - -namespace Managing.Application.Workflows; - -public class WorkflowService : IWorkflowService -{ - private readonly IWorkflowRepository _workflowRepository; - private readonly IExchangeService _exchangeService; - private readonly IFlowFactory _flowFactory; - private readonly ICacheService _cacheService; - private readonly ITradingService _tradingService; - private readonly IAccountService _accountService; - - public WorkflowService( - IWorkflowRepository workflowRepository, - IExchangeService exchangeService, - IFlowFactory flowFactory, - ICacheService cacheService, - ITradingService tradingService, - IAccountService accountService) - { - _workflowRepository = workflowRepository; - _exchangeService = exchangeService; - _flowFactory = flowFactory; - _cacheService = cacheService; - _tradingService = tradingService; - _accountService = accountService; - } - - public async Task InsertOrUpdateWorkflow(SyntheticWorkflow workflowRequest) - { - var previousWorkflow = await _workflowRepository.GetWorkflow(workflowRequest.Name); - - try - { - if (previousWorkflow != null) - { - await _workflowRepository.UpdateWorkflow(workflowRequest); - } - else - { - await _workflowRepository.InsertWorkflow(workflowRequest); - } - } - catch (Exception ex) - { - throw; - } - - return Map(workflowRequest); - } - - private Workflow Map(SyntheticWorkflow workflowRequest) - { - var workflow = new Workflow - { - Name = workflowRequest.Name, - Usage = workflowRequest.Usage, - Description = workflowRequest.Description, - Flows = new List() - }; - - // Add first flow that dont have any parent - var firstFlow = workflowRequest.Flows.SingleOrDefault(f => string.IsNullOrEmpty(f.ParentId)); - - if (firstFlow != null) - { - var flow = Build(workflowRequest.Flows, firstFlow); - workflow.Flows.Add(flow); - } - else - { - // TODO : Throw exception - throw new Exception("No first flow found"); - } - - return workflow; - } - - private IFlow Build(List flows, SyntheticFlow firstFlow) - { - var flow = _flowFactory.BuildFlow(firstFlow); - - foreach (var flowRequest in flows.Where(f => f.ParentId == firstFlow.Id)) - { - flow.Children.Add(Build(flows, flowRequest)); - } - - return flow; - } - - public IEnumerable GetWorkflows() - { - return _workflowRepository.GetWorkflows(); - } - - public bool DeleteWorkflow(string name) - { - return _workflowRepository.DeleteWorkflow(name); - } - - public async Task GetWorkflow(string name) - { - return Map(await _workflowRepository.GetWorkflow(name)); - } - - public Task> GetAvailableFlows() - { - var availableFlows = new List - { - new FeedTicker(_exchangeService), - new RsiDiv(), - new OpenPosition(_exchangeService, _cacheService, _accountService, _tradingService) - }; - - return Task.FromResult(availableFlows.AsEnumerable()); - } -} \ No newline at end of file diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs index 23f763c..096893c 100644 --- a/src/Managing.Bootstrap/ApiBootstrap.cs +++ b/src/Managing.Bootstrap/ApiBootstrap.cs @@ -21,15 +21,13 @@ using Managing.Application.Trading.Commands; using Managing.Application.Users; using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; -using Managing.Application.Workflows; using Managing.Domain.Trades; using Managing.Infrastructure.Databases; using Managing.Infrastructure.Databases.InfluxDb; using Managing.Infrastructure.Databases.InfluxDb.Abstractions; using Managing.Infrastructure.Databases.InfluxDb.Models; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Configurations; +using Managing.Infrastructure.Databases.PostgreSql; +using Managing.Infrastructure.Databases.PostgreSql.Configurations; using Managing.Infrastructure.Evm; using Managing.Infrastructure.Evm.Abstractions; using Managing.Infrastructure.Evm.Models.Privy; @@ -58,60 +56,57 @@ public static class ApiBootstrap services.Configure(configuration.GetSection("Web3Proxy")); return services - .AddWorkers(configuration) .AddApplication() .AddInfrastructure(configuration) + .AddWorkers(configuration) .AddFluentValidation() .AddMediatR(); } private static IServiceCollection AddApplication(this IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddTransient, OpenPositionCommandHandler>(); services.AddTransient, ClosePositionCommandHandler>(); // Processors + services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + + services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + return services; } private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { // Database - services.AddSingleton(sp => - sp.GetRequiredService>().Value); - - services.AddTransient(typeof(IMongoRepository<>), typeof(MongoRepository<>)); + services.AddSingleton(sp => + sp.GetRequiredService>().Value); services.AddSingleton(sp => sp.GetRequiredService>().Value); @@ -128,30 +123,29 @@ public static class ApiBootstrap services.AddChainlinkGmx(); services.AddSingleton(); - // Repositories + // PostgreSql Repositories + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // InfluxDb Repositories services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddTransient(); - services.AddTransient(); // Cache services.AddDistributedMemoryCache(); services.AddTransient(); services.AddSingleton(); - // Index Service - services.AddSingleton(); - // Services services.AddTransient(); services.AddSingleton(); diff --git a/src/Managing.Bootstrap/Managing.Bootstrap.csproj b/src/Managing.Bootstrap/Managing.Bootstrap.csproj index 09a8641..9c5f515 100644 --- a/src/Managing.Bootstrap/Managing.Bootstrap.csproj +++ b/src/Managing.Bootstrap/Managing.Bootstrap.csproj @@ -1,28 +1,28 @@ - - net8.0 - enable - AnyCPU;x64 - + + net8.0 + enable + AnyCPU;x64 + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/src/Managing.Bootstrap/WorkersBootstrap.cs b/src/Managing.Bootstrap/WorkersBootstrap.cs index 3052530..5c18c15 100644 --- a/src/Managing.Bootstrap/WorkersBootstrap.cs +++ b/src/Managing.Bootstrap/WorkersBootstrap.cs @@ -1,8 +1,4 @@ -using Binance.Net.Clients; -using Binance.Net.Interfaces.Clients; -using Kraken.Net.Clients; -using Kraken.Net.Interfaces.Clients; -using Managing.Application; +using Managing.Application; using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; @@ -24,9 +20,8 @@ using Managing.Infrastructure.Databases; using Managing.Infrastructure.Databases.InfluxDb; using Managing.Infrastructure.Databases.InfluxDb.Abstractions; using Managing.Infrastructure.Databases.InfluxDb.Models; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Configurations; +using Managing.Infrastructure.Databases.PostgreSql; +using Managing.Infrastructure.Databases.PostgreSql.Configurations; using Managing.Infrastructure.Evm; using Managing.Infrastructure.Evm.Abstractions; using Managing.Infrastructure.Evm.Models.Privy; @@ -50,31 +45,46 @@ public static class WorkersBootstrap { return services .AddApplication() - .AddWorkers(configuration) - .AddInfrastructure(configuration); + .AddInfrastructure(configuration) + .AddWorkers(configuration); } private static IServiceCollection AddApplication(this IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddTransient, OpenPositionCommandHandler>(); services.AddTransient, ClosePositionCommandHandler>(); + // Processors + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + return services; } @@ -106,12 +116,6 @@ public static class WorkersBootstrap services.AddHostedService(); } - // Other Workers - if (configuration.GetValue("WorkerFee", false)) - { - services.AddHostedService(); - } - if (configuration.GetValue("WorkerSpotlight", false)) { services.AddHostedService(); @@ -148,9 +152,8 @@ public static class WorkersBootstrap private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { // Database - services.AddSingleton(sp => - sp.GetRequiredService>().Value); - services.AddTransient(typeof(IMongoRepository<>), typeof(MongoRepository<>)); + services.AddSingleton(sp => + sp.GetRequiredService>().Value); services.AddSingleton(sp => sp.GetRequiredService>().Value); @@ -170,25 +173,23 @@ public static class WorkersBootstrap // Repositories services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // Cache services.AddDistributedMemoryCache(); services.AddTransient(); services.AddTransient(); - // Index Service - services.AddSingleton(); // Processors services.AddTransient(); @@ -196,8 +197,6 @@ public static class WorkersBootstrap // Web Clients services.AddTransient(); services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Managing.Common/Constants.cs b/src/Managing.Common/Constants.cs index ffce839..7cf1619 100644 --- a/src/Managing.Common/Constants.cs +++ b/src/Managing.Common/Constants.cs @@ -20,7 +20,7 @@ public class Databases { public const string InfluxDb = "InfluxDb"; - public const string MongoDb = "ManagingDatabase"; + public const string PostgreSql = "PostgreSql"; } public class ThirdParty diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs index d9b4a59..5d87bf9 100644 --- a/src/Managing.Common/Enums.cs +++ b/src/Managing.Common/Enums.cs @@ -199,6 +199,7 @@ public static class Enums status == TradeStatus.Filled; + [Flags] public enum PositionStatus { New = 0, diff --git a/src/Managing.Core/ServiceScopeHelpers.cs b/src/Managing.Core/ServiceScopeHelpers.cs new file mode 100644 index 0000000..e387f75 --- /dev/null +++ b/src/Managing.Core/ServiceScopeHelpers.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Managing.Core +{ + public static class ServiceScopeHelpers + { + public static async Task WithScopedService(IServiceScopeFactory scopeFactory, Func action) + where TService : notnull + { + using var scope = scopeFactory.CreateScope(); + var service = scope.ServiceProvider.GetRequiredService(); + await action(service); + } + + public static async Task WithScopedService(IServiceScopeFactory scopeFactory, Func> action) + where TService : notnull + { + using var scope = scopeFactory.CreateScope(); + var service = scope.ServiceProvider.GetRequiredService(); + return await action(service); + } + + public static async Task WithScopedServices(IServiceScopeFactory scopeFactory, Func action) + where T1 : notnull + where T2 : notnull + { + using var scope = scopeFactory.CreateScope(); + var s1 = scope.ServiceProvider.GetRequiredService(); + var s2 = scope.ServiceProvider.GetRequiredService(); + await action(s1, s2); + } + + public static async Task WithScopedServices(IServiceScopeFactory scopeFactory, Func> action) + where T1 : notnull + where T2 : notnull + { + using var scope = scopeFactory.CreateScope(); + var s1 = scope.ServiceProvider.GetRequiredService(); + var s2 = scope.ServiceProvider.GetRequiredService(); + return await action(s1, s2); + } + + public static async Task WithScopedServices(IServiceScopeFactory scopeFactory, Func action) + where T1 : notnull + where T2 : notnull + where T3 : notnull + { + using var scope = scopeFactory.CreateScope(); + var s1 = scope.ServiceProvider.GetRequiredService(); + var s2 = scope.ServiceProvider.GetRequiredService(); + var s3 = scope.ServiceProvider.GetRequiredService(); + await action(s1, s2, s3); + } + + public static async Task WithScopedServices(IServiceScopeFactory scopeFactory, Func> action) + where T1 : notnull + where T2 : notnull + where T3 : notnull + { + using var scope = scopeFactory.CreateScope(); + var s1 = scope.ServiceProvider.GetRequiredService(); + var s2 = scope.ServiceProvider.GetRequiredService(); + var s3 = scope.ServiceProvider.GetRequiredService(); + return await action(s1, s2, s3); + } + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Backtests/Backtest.cs b/src/Managing.Domain/Backtests/Backtest.cs index dffa0bf..60be133 100644 --- a/src/Managing.Domain/Backtests/Backtest.cs +++ b/src/Managing.Domain/Backtests/Backtest.cs @@ -3,8 +3,6 @@ using System.ComponentModel.DataAnnotations; using Exilion.TradingAtomics; using Managing.Domain.Bots; using Managing.Domain.Candles; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; using Managing.Domain.Trades; using Managing.Domain.Users; @@ -17,7 +15,7 @@ public class Backtest public Backtest( TradingBotConfig config, List positions, - List signals, + List signals, List candles = null) { Config = config; @@ -47,14 +45,13 @@ public class Backtest [Required] public decimal HodlPercentage { get; set; } [Required] public TradingBotConfig Config { get; } [Required] public List Positions { get; } - [Required] public List Signals { get; } + [Required] public List Signals { get; } [Required] public List Candles { get; set; } [Required] public DateTime StartDate { get; set; } [Required] public DateTime EndDate { get; set; } [Required] public PerformanceMetrics Statistics { get; set; } [Required] public decimal Fees { get; set; } [Required] public List> WalletBalances { get; set; } - [Required] public MoneyManagement OptimizedMoneyManagement { get; set; } [Required] public User User { get; set; } [Required] public Dictionary IndicatorsValues { get; set; } [Required] public double Score { get; set; } @@ -75,7 +72,7 @@ public class Backtest string accountName, string botName, decimal initialTradingBalance, - MoneyManagement moneyManagement = null) + LightMoneyManagement moneyManagement = null) { return new TradingBotConfig { @@ -100,16 +97,16 @@ public class Backtest /// Creates a copy of the backtest's configuration for a new backtest. /// Useful for running similar backtests with modified parameters. /// - /// New start date for the backtest - /// New end date for the backtest - /// New initial balance for the backtest + /// Start date for the new backtest + /// End date for the new backtest + /// Initial balance for the new backtest /// Optional money management override /// A new TradingBotConfig for backtesting public TradingBotConfig CreateBacktestConfig( DateTime startDate, DateTime endDate, decimal balance, - MoneyManagement moneyManagement = null) + LightMoneyManagement moneyManagement = null) { return new TradingBotConfig { @@ -123,10 +120,10 @@ public class Backtest IsForBacktest = true, CooldownPeriod = Config.CooldownPeriod, MaxLossStreak = Config.MaxLossStreak, - MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value + MaxPositionTimeHours = Config.MaxPositionTimeHours, FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit, FlipPosition = Config.FlipPosition, - Name = $"Backtest-{Config.ScenarioName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}" + Name = Config.Name }; } diff --git a/src/Managing.Domain/Backtests/BacktestScoringParams.cs b/src/Managing.Domain/Backtests/BacktestScoringParams.cs index 37844f7..a22cf6d 100644 --- a/src/Managing.Domain/Backtests/BacktestScoringParams.cs +++ b/src/Managing.Domain/Backtests/BacktestScoringParams.cs @@ -1,4 +1,3 @@ -using Managing.Domain.MoneyManagements; using static Managing.Common.Enums; namespace Managing.Domain.Backtests; @@ -21,7 +20,7 @@ public class BacktestScoringParams public DateTime StartDate { get; } public DateTime EndDate { get; } public Timeframe Timeframe { get; } - public MoneyManagement MoneyManagement { get; } + public LightMoneyManagement MoneyManagement { get; } public BacktestScoringParams( double sharpeRatio, @@ -38,7 +37,7 @@ public class BacktestScoringParams DateTime startDate = default, DateTime endDate = default, Timeframe timeframe = Timeframe.OneHour, - MoneyManagement moneyManagement = null) + LightMoneyManagement moneyManagement = null) { SharpeRatio = sharpeRatio; GrowthPercentage = growthPercentage; diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs index 96f53dc..6f13a76 100644 --- a/src/Managing.Domain/Bots/Bot.cs +++ b/src/Managing.Domain/Bots/Bot.cs @@ -58,7 +58,6 @@ namespace Managing.Domain.Bots { await action(); ExecutionCount++; - await Task.Delay(Interval, CancellationToken.Token); if (CancellationToken.IsCancellationRequested) break; } @@ -72,6 +71,10 @@ namespace Managing.Domain.Bots SentrySdk.CaptureException(ex); Console.WriteLine(ex.Message); } + finally + { + await Task.Delay(Interval, CancellationToken.Token); + } } }, CancellationToken.Token); } @@ -84,7 +87,7 @@ namespace Managing.Domain.Bots public void Stop() { Status = BotStatus.Down; - SaveBackup(); + _ = Task.Run(async () => await SaveBackup()); // CancellationToken.Cancel(); } @@ -116,7 +119,7 @@ namespace Managing.Domain.Bots return DateTime.UtcNow - StartupTime; } - public abstract void SaveBackup(); + public abstract Task SaveBackup(); public abstract void LoadBackup(BotBackup backup); } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/BotBackup.cs b/src/Managing.Domain/Bots/BotBackup.cs index 1f56f14..792d2ae 100644 --- a/src/Managing.Domain/Bots/BotBackup.cs +++ b/src/Managing.Domain/Bots/BotBackup.cs @@ -1,4 +1,5 @@ using Managing.Domain.Users; +using Newtonsoft.Json; using static Managing.Common.Enums; namespace Managing.Domain.Bots; @@ -7,7 +8,33 @@ public class BotBackup { public string Identifier { get; set; } public User User { get; set; } - public string Data { get; set; } + public TradingBotBackup Data { get; set; } public BotStatus LastStatus { get; set; } public DateTime CreateDate { get; set; } + + /// + /// Serializes the TradingBotBackup data to JSON string + /// + /// JSON string representation of the data + public string SerializeData() + { + if (Data == null) return null; + + return JsonConvert.SerializeObject(Data); + } + + /// + /// Deserializes JSON string to TradingBotBackup data + /// + /// JSON string to deserialize + public void DeserializeData(string jsonData) + { + if (string.IsNullOrEmpty(jsonData)) + { + Data = null; + return; + } + + Data = JsonConvert.DeserializeObject(jsonData); + } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/IBot.cs b/src/Managing.Domain/Bots/IBot.cs index 0ef3c11..70c2f1d 100644 --- a/src/Managing.Domain/Bots/IBot.cs +++ b/src/Managing.Domain/Bots/IBot.cs @@ -24,7 +24,7 @@ namespace Managing.Domain.Bots DateTime StartupTime { get; } string Identifier { get; set; } - void SaveBackup(); + Task SaveBackup(); void LoadBackup(BotBackup backup); } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/TradingBotBackup.cs b/src/Managing.Domain/Bots/TradingBotBackup.cs new file mode 100644 index 0000000..ac5c538 --- /dev/null +++ b/src/Managing.Domain/Bots/TradingBotBackup.cs @@ -0,0 +1,36 @@ +using Managing.Domain.Trades; + +namespace Managing.Domain.Bots; + +public class TradingBotBackup +{ + /// + /// The complete trading bot configuration + /// + public TradingBotConfig Config { get; set; } + + /// + /// Runtime state: Active signals for the bot + /// + public HashSet Signals { get; set; } + + /// + /// Runtime state: Open and closed positions for the bot + /// + public List Positions { get; set; } + + /// + /// Runtime state: Historical wallet balances over time + /// + public Dictionary WalletBalances { get; set; } + + /// + /// Runtime state: When the bot was started + /// + public DateTime StartupTime { get; set; } + + /// + /// Runtime state: When the bot was created + /// + public DateTime CreateDate { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/TradingBotConfig.cs b/src/Managing.Domain/Bots/TradingBotConfig.cs index 71c121e..1af42ef 100644 --- a/src/Managing.Domain/Bots/TradingBotConfig.cs +++ b/src/Managing.Domain/Bots/TradingBotConfig.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Managing.Domain.MoneyManagements; using Managing.Domain.Risk; using Managing.Domain.Scenarios; using static Managing.Common.Enums; @@ -9,7 +8,7 @@ namespace Managing.Domain.Bots; public class TradingBotConfig { [Required] public string AccountName { get; set; } - [Required] public MoneyManagement MoneyManagement { get; set; } + [Required] public LightMoneyManagement MoneyManagement { get; set; } [Required] public Ticker Ticker { get; set; } [Required] public Timeframe Timeframe { get; set; } [Required] public bool IsForWatchingOnly { get; set; } diff --git a/src/Managing.Domain/Candles/Candle.cs b/src/Managing.Domain/Candles/Candle.cs index c859d95..47dbe5b 100644 --- a/src/Managing.Domain/Candles/Candle.cs +++ b/src/Managing.Domain/Candles/Candle.cs @@ -1,34 +1,20 @@ -using Managing.Common; +using System.ComponentModel.DataAnnotations; +using Managing.Common; using Skender.Stock.Indicators; -using System.ComponentModel.DataAnnotations; namespace Managing.Domain.Candles { public class Candle : IQuote { - [Required] - public Enums.TradingExchanges Exchange { get; set; } - [Required] - public string Ticker { get; set; } - [Required] - public DateTime OpenTime { get; set; } - [Required] - public DateTime Date { get; set; } - [Required] - public decimal Open { get; set; } - [Required] - public decimal Close { get; set; } - public decimal Volume { get; } - [Required] - public decimal High { get; set; } - [Required] - public decimal Low { get; set; } - public decimal BaseVolume { get; set; } - public decimal QuoteVolume { get; set; } - public int TradeCount { get; set; } - public decimal TakerBuyBaseVolume { get; set; } - public decimal TakerBuyQuoteVolume { get; set; } - [Required] - public Enums.Timeframe Timeframe { get; set; } + [Required] public Enums.TradingExchanges Exchange { get; set; } + [Required] public string Ticker { get; set; } + [Required] public DateTime OpenTime { get; set; } + [Required] public DateTime Date { get; set; } + [Required] public decimal Open { get; set; } + [Required] public decimal Close { get; set; } + [Required] public decimal High { get; set; } + [Required] public decimal Low { get; set; } + [Required] public Enums.Timeframe Timeframe { get; set; } + public decimal Volume { get; set; } } -} +} \ No newline at end of file diff --git a/src/Managing.Domain/Managing.Domain.csproj b/src/Managing.Domain/Managing.Domain.csproj index a853436..8d0bdf1 100644 --- a/src/Managing.Domain/Managing.Domain.csproj +++ b/src/Managing.Domain/Managing.Domain.csproj @@ -1,19 +1,20 @@ - - net8.0 - enable - AnyCPU;x64 - + + net8.0 + enable + AnyCPU;x64 + - - - - + + + + + - - - - + + + + diff --git a/src/Managing.Domain/MoneyManagements/LightMoneyManagement.cs b/src/Managing.Domain/MoneyManagements/LightMoneyManagement.cs new file mode 100644 index 0000000..35ce875 --- /dev/null +++ b/src/Managing.Domain/MoneyManagements/LightMoneyManagement.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +public class LightMoneyManagement +{ + [Required] public string Name { get; set; } + [Required] public Timeframe Timeframe { get; set; } + [Required] public decimal StopLoss { get; set; } + [Required] public decimal TakeProfit { get; set; } + [Required] public decimal Leverage { get; set; } + + public void FormatPercentage() + { + StopLoss /= 100; + TakeProfit /= 100; + } +} \ No newline at end of file diff --git a/src/Managing.Domain/MoneyManagements/MoneyManagement.cs b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs index 469e53f..b1ddc00 100644 --- a/src/Managing.Domain/MoneyManagements/MoneyManagement.cs +++ b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs @@ -1,28 +1,9 @@ -using System.ComponentModel.DataAnnotations; -using Managing.Domain.Users; -using static Managing.Common.Enums; +using Managing.Domain.Users; namespace Managing.Domain.MoneyManagements { - public class MoneyManagement + public class MoneyManagement : LightMoneyManagement { - [Required] - public string Name { get; set; } - [Required] - public Timeframe Timeframe { get; set; } - [Required] - public decimal StopLoss { get; set; } - [Required] - public decimal TakeProfit { get; set; } - [Required] - public decimal Leverage { get; set; } - public User User { get; set; } - - public void FormatPercentage() - { - StopLoss /= 100; - TakeProfit /= 100; - } } } diff --git a/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs index 95e4423..57bd2ab 100644 --- a/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs +++ b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs @@ -1,18 +1,17 @@ -using Managing.Domain.MoneyManagements; -using static Managing.Common.Enums; +using static Managing.Common.Enums; namespace Managing.Domain.Shared.Helpers { public static class RiskHelpers { - public static decimal GetStopLossPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement) + public static decimal GetStopLossPrice(TradeDirection direction, decimal price, LightMoneyManagement moneyManagement) { return direction == TradeDirection.Long ? price -= price * moneyManagement.StopLoss : price += price * moneyManagement.StopLoss; } - public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1) + public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, LightMoneyManagement moneyManagement, int count = 1) { decimal percentage = moneyManagement.TakeProfit * count; diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs index f3c6c98..24de0fd 100644 --- a/src/Managing.Domain/Shared/Helpers/TradingBox.cs +++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs @@ -49,16 +49,16 @@ public static class TradingBox { private static readonly IndicatorComboConfig _defaultConfig = new(); - public static Signal GetSignal(HashSet newCandles, HashSet strategies, - HashSet previousSignal, int? loopbackPeriod = 1) + public static LightSignal GetSignal(HashSet newCandles, HashSet strategies, + HashSet previousSignal, int? loopbackPeriod = 1) { return GetSignal(newCandles, strategies, previousSignal, _defaultConfig, loopbackPeriod); } - public static Signal GetSignal(HashSet newCandles, HashSet strategies, - HashSet previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1) + public static LightSignal GetSignal(HashSet newCandles, HashSet strategies, + HashSet previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1) { - var signalOnCandles = new List(); + var signalOnCandles = new List(); var limitedCandles = newCandles.ToList().TakeLast(600).ToList(); foreach (var strategy in strategies) @@ -126,13 +126,15 @@ public static class TradingBox data.Timeframe, config); } - public static Signal ComputeSignals(HashSet strategies, HashSet signalOnCandles, Ticker ticker, + public static LightSignal ComputeSignals(HashSet strategies, HashSet signalOnCandles, + Ticker ticker, Timeframe timeframe) { return ComputeSignals(strategies, signalOnCandles, ticker, timeframe, _defaultConfig); } - public static Signal ComputeSignals(HashSet strategies, HashSet signalOnCandles, Ticker ticker, + public static LightSignal ComputeSignals(HashSet strategies, HashSet signalOnCandles, + Ticker ticker, Timeframe timeframe, IndicatorComboConfig config) { if (strategies.Count == 1) @@ -194,7 +196,7 @@ public static class TradingBox var lastSignal = signals.LastOrDefault() ?? trendSignals.LastOrDefault() ?? contextSignals.LastOrDefault(); - return new Signal( + return new LightSignal( ticker, finalDirection, averageConfidence, @@ -208,7 +210,7 @@ public static class TradingBox /// /// Calculates the average confidence level from a list of signals /// - private static Confidence CalculateAverageConfidence(List signals) + private static Confidence CalculateAverageConfidence(List signals) { if (!signals.Any()) { @@ -231,7 +233,7 @@ public static class TradingBox /// /// Validates context strategies based on confidence levels indicating market condition quality /// - private static bool ValidateContextStrategies(HashSet allStrategies, List contextSignals, + private static bool ValidateContextStrategies(HashSet allStrategies, List contextSignals, IndicatorComboConfig config) { var contextStrategiesCount = allStrategies.Count(s => s.SignalType == SignalType.Context); diff --git a/src/Managing.Domain/Statistics/Spotlight.cs b/src/Managing.Domain/Statistics/Spotlight.cs index 71b82b7..9b6ace7 100644 --- a/src/Managing.Domain/Statistics/Spotlight.cs +++ b/src/Managing.Domain/Statistics/Spotlight.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using Managing.Common; using Managing.Domain.Scenarios; -using Managing.Domain.Strategies; namespace Managing.Domain.Statistics; @@ -22,9 +21,9 @@ public class Spotlight public class TickerSignal { [Required] public Enums.Ticker Ticker { get; set; } - [Required] public List FiveMinutes { get; set; } - [Required] public List FifteenMinutes { get; set; } - [Required] public List OneHour { get; set; } - [Required] public List FourHour { get; set; } - [Required] public List OneDay { get; set; } + [Required] public List FiveMinutes { get; set; } + [Required] public List FifteenMinutes { get; set; } + [Required] public List OneHour { get; set; } + [Required] public List FourHour { get; set; } + [Required] public List OneDay { get; set; } } \ No newline at end of file diff --git a/src/Managing.Domain/Strategies/Context/StDevContext.cs b/src/Managing.Domain/Strategies/Context/StDevContext.cs index 961e127..3b7eac5 100644 --- a/src/Managing.Domain/Strategies/Context/StDevContext.cs +++ b/src/Managing.Domain/Strategies/Context/StDevContext.cs @@ -9,15 +9,15 @@ namespace Managing.Domain.Strategies.Context; public class StDevContext : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public StDevContext(string name, int period) : base(name, IndicatorType.StDev) { - Signals = new List(); + Signals = new List(); Period = period; } - public override List Run() + public override List Run() { if (Candles.Count <= Period) { @@ -112,7 +112,7 @@ public class StDevContext : Indicator private void AddSignal(CandleStDev candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal( + var signal = new LightSignal( MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, diff --git a/src/Managing.Domain/Strategies/IIndicator.cs b/src/Managing.Domain/Strategies/IIndicator.cs index b280e5e..7f6c319 100644 --- a/src/Managing.Domain/Strategies/IIndicator.cs +++ b/src/Managing.Domain/Strategies/IIndicator.cs @@ -16,7 +16,7 @@ namespace Managing.Domain.Strategies int? SignalPeriods { get; set; } FixedSizeQueue Candles { get; set; } - List Run(); + List Run(); IndicatorsResultBase GetIndicatorValues(); void UpdateCandles(HashSet newCandles); string GetName(); diff --git a/src/Managing.Domain/Strategies/Indicator.cs b/src/Managing.Domain/Strategies/Indicator.cs index d0054b1..5347e1d 100644 --- a/src/Managing.Domain/Strategies/Indicator.cs +++ b/src/Managing.Domain/Strategies/Indicator.cs @@ -34,9 +34,9 @@ namespace Managing.Domain.Strategies public int? CyclePeriods { get; set; } public User User { get; set; } - public virtual List Run() + public virtual List Run() { - return new List(); + return new List(); } public virtual IndicatorsResultBase GetIndicatorValues() diff --git a/src/Managing.Domain/Strategies/LightSignal.cs b/src/Managing.Domain/Strategies/LightSignal.cs new file mode 100644 index 0000000..d238fcd --- /dev/null +++ b/src/Managing.Domain/Strategies/LightSignal.cs @@ -0,0 +1,51 @@ +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using Managing.Core; +using Managing.Domain.Candles; +using static Managing.Common.Enums; + +public class LightSignal : ValueObject +{ + public LightSignal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date, + TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName) + { + Direction = direction; + Confidence = confidence; + Candle = candle; + Date = date; + Ticker = ticker; + Exchange = exchange; + Status = SignalStatus.WaitingForPosition; + IndicatorType = indicatorType; + IndicatorName = indicatorName; + SignalType = signalType; + + Identifier = + $"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}"; + } + + [Required] public SignalStatus Status { get; set; } + [Required] public TradeDirection Direction { get; } + [Required] public Confidence Confidence { get; set; } + [Required] public Timeframe Timeframe { get; } + [Required] public DateTime Date { get; private set; } + [Required] public Candle Candle { get; } + [Required] public string Identifier { get; } + [Required] public Ticker Ticker { get; } + [Required] public TradingExchanges Exchange { get; set; } + [Required] public IndicatorType IndicatorType { get; set; } + [Required] public SignalType SignalType { get; set; } + [Required] public string IndicatorName { get; set; } + + protected override IEnumerable GetEqualityComponents() + { + yield return Direction; + yield return Confidence; + yield return Date; + } + + public void SetConfidence(Confidence confidence) + { + Confidence = confidence; + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Strategies/Signal.cs b/src/Managing.Domain/Strategies/Signal.cs index 05a6c00..e918e83 100644 --- a/src/Managing.Domain/Strategies/Signal.cs +++ b/src/Managing.Domain/Strategies/Signal.cs @@ -1,14 +1,20 @@ using System.ComponentModel.DataAnnotations; -using System.Globalization; -using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.Users; using static Managing.Common.Enums; namespace Managing.Domain.Strategies { - public class Signal : ValueObject + public class Signal : LightSignal { + public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date, + TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName, + User user) + : base(ticker, direction, confidence, candle, date, exchange, indicatorType, signalType, indicatorName) + { + User = user; + } + [Required] public SignalStatus Status { get; set; } [Required] public TradeDirection Direction { get; } [Required] public Confidence Confidence { get; private set; } @@ -22,37 +28,5 @@ namespace Managing.Domain.Strategies [Required] public SignalType SignalType { get; set; } public User User { get; set; } [Required] public string IndicatorName { get; set; } - - public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date, - TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName, - User user = null) - { - Direction = direction; - Confidence = confidence; - Candle = candle; - Date = date; - Ticker = ticker; - Exchange = exchange; - Status = SignalStatus.WaitingForPosition; - IndicatorType = indicatorType; - User = user; - IndicatorName = indicatorName; - SignalType = signalType; - - Identifier = - $"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}"; - } - - public void SetConfidence(Confidence confidence) - { - Confidence = confidence; - } - - protected override IEnumerable GetEqualityComponents() - { - yield return Direction; - yield return Confidence; - yield return Date; - } } } \ No newline at end of file diff --git a/src/Managing.Domain/Strategies/Signals/ChandelierExitIndicator.cs b/src/Managing.Domain/Strategies/Signals/ChandelierExitIndicator.cs index d99e8c5..a5bdb6d 100644 --- a/src/Managing.Domain/Strategies/Signals/ChandelierExitIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/ChandelierExitIndicator.cs @@ -9,18 +9,18 @@ namespace Managing.Domain.Strategies.Signals; public class ChandelierExitIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public ChandelierExitIndicator(string name, int period, double multiplier) : base(name, IndicatorType.ChandelierExit) { - Signals = new List(); + Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 1 + Period.Value; } - public override List Run() + public override List Run() { if (Candles.Count <= MinimumHistory) { @@ -106,7 +106,7 @@ public class ChandelierExitIndicator : Indicator private void AddSignal(CandleChandelier candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal( + var signal = new LightSignal( MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, diff --git a/src/Managing.Domain/Strategies/Signals/DualEmaCrossIndicator.cs b/src/Managing.Domain/Strategies/Signals/DualEmaCrossIndicator.cs index 867e0b1..70d08eb 100644 --- a/src/Managing.Domain/Strategies/Signals/DualEmaCrossIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/DualEmaCrossIndicator.cs @@ -9,11 +9,11 @@ namespace Managing.Domain.Strategies.Signals; public class DualEmaCrossIndicator : EmaBaseIndicator { - public List Signals { get; set; } + public List Signals { get; set; } public DualEmaCrossIndicator(string name, int fastPeriod, int slowPeriod) : base(name, IndicatorType.DualEmaCross) { - Signals = new List(); + Signals = new List(); FastPeriods = fastPeriod; SlowPeriods = slowPeriod; MinimumHistory = Math.Max(fastPeriod, slowPeriod) * 2; @@ -28,7 +28,7 @@ public class DualEmaCrossIndicator : EmaBaseIndicator }; } - public override List Run() + public override List Run() { if (Candles.Count <= MinimumHistory) { @@ -103,7 +103,7 @@ public class DualEmaCrossIndicator : EmaBaseIndicator private void AddSignal(CandleDualEma candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { diff --git a/src/Managing.Domain/Strategies/Signals/EmaCrossIndicator.cs b/src/Managing.Domain/Strategies/Signals/EmaCrossIndicator.cs index d7e26f4..f45795e 100644 --- a/src/Managing.Domain/Strategies/Signals/EmaCrossIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/EmaCrossIndicator.cs @@ -8,11 +8,11 @@ namespace Managing.Domain.Strategies.Signals; public class EmaCrossIndicator : EmaBaseIndicator { - public List Signals { get; set; } + public List Signals { get; set; } public EmaCrossIndicator(string name, int period) : base(name, IndicatorType.EmaCross) { - Signals = new List(); + Signals = new List(); Period = period; } @@ -24,7 +24,7 @@ public class EmaCrossIndicator : EmaBaseIndicator }; } - public override List Run() + public override List Run() { if (Candles.Count <= Period) { @@ -67,7 +67,7 @@ public class EmaCrossIndicator : EmaBaseIndicator private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { diff --git a/src/Managing.Domain/Strategies/Signals/LaggingSTC.cs b/src/Managing.Domain/Strategies/Signals/LaggingSTC.cs index 4151f44..edf7850 100644 --- a/src/Managing.Domain/Strategies/Signals/LaggingSTC.cs +++ b/src/Managing.Domain/Strategies/Signals/LaggingSTC.cs @@ -16,18 +16,18 @@ namespace Managing.Domain.Strategies.Signals; /// public class LaggingSTC : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public LaggingSTC(string name, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name, IndicatorType.LaggingStc) { - Signals = new List(); + Signals = new List(); FastPeriods = fastPeriods; SlowPeriods = slowPeriods; CyclePeriods = cyclePeriods; } - public override List Run() + public override List Run() { if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods)) { @@ -123,7 +123,7 @@ public class LaggingSTC : Indicator private void AddSignal(CandleSct candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal( + var signal = new LightSignal( MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, diff --git a/src/Managing.Domain/Strategies/Signals/MacdCrossIndicator.cs b/src/Managing.Domain/Strategies/Signals/MacdCrossIndicator.cs index 0c29667..125a5ca 100644 --- a/src/Managing.Domain/Strategies/Signals/MacdCrossIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/MacdCrossIndicator.cs @@ -9,18 +9,18 @@ namespace Managing.Domain.Strategies.Signals; public class MacdCrossIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public MacdCrossIndicator(string name, int fastPeriods, int slowPeriods, int signalPeriods) : base(name, IndicatorType.MacdCross) { - Signals = new List(); + Signals = new List(); FastPeriods = fastPeriods; SlowPeriods = slowPeriods; SignalPeriods = signalPeriods; } - public override List Run() + public override List Run() { if (Candles.Count <= 2 * (SlowPeriods + SignalPeriods)) { @@ -104,7 +104,7 @@ public class MacdCrossIndicator : Indicator private void AddSignal(CandleMacd candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { diff --git a/src/Managing.Domain/Strategies/Signals/RsiDivergenceConfirmIndicator.cs b/src/Managing.Domain/Strategies/Signals/RsiDivergenceConfirmIndicator.cs index 872d0dc..1172649 100644 --- a/src/Managing.Domain/Strategies/Signals/RsiDivergenceConfirmIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/RsiDivergenceConfirmIndicator.cs @@ -9,19 +9,19 @@ namespace Managing.Domain.Strategies.Signals; public class RsiDivergenceConfirmIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public RsiDivergenceConfirmIndicator(string name, int period) : base(name, IndicatorType.RsiDivergenceConfirm) { Period = period; - Signals = new List(); + Signals = new List(); } /// /// Get RSI signals /// /// - public override List Run() + public override List Run() { if (Candles.Count <= Period) { @@ -232,7 +232,7 @@ public class RsiDivergenceConfirmIndicator : Indicator private void AddSignal(CandleRsi candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { diff --git a/src/Managing.Domain/Strategies/Signals/RsiDivergenceIndicator.cs b/src/Managing.Domain/Strategies/Signals/RsiDivergenceIndicator.cs index 0e584d3..2ceacc3 100644 --- a/src/Managing.Domain/Strategies/Signals/RsiDivergenceIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/RsiDivergenceIndicator.cs @@ -9,7 +9,7 @@ namespace Managing.Domain.Strategies.Signals; public class RsiDivergenceIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public TradeDirection Direction { get; set; } private const int UpperBand = 70; private const int LowerBand = 30; @@ -17,14 +17,14 @@ public class RsiDivergenceIndicator : Indicator public RsiDivergenceIndicator(string name, int period) : base(name, IndicatorType.RsiDivergence) { Period = period; - Signals = new List(); + Signals = new List(); } /// /// Get RSI signals /// /// - public override List Run() + public override List Run() { if (!Period.HasValue || Candles.Count <= Period) { @@ -205,7 +205,7 @@ public class RsiDivergenceIndicator : Indicator private void AddSignal(CandleRsi candleSignal, TradeDirection direction) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, Confidence.Low, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, Confidence.Low, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (Signals.Count(s => s.Identifier == signal.Identifier) < 1) diff --git a/src/Managing.Domain/Strategies/Signals/StcIndicator.cs b/src/Managing.Domain/Strategies/Signals/StcIndicator.cs index b8280f1..d89dcd3 100644 --- a/src/Managing.Domain/Strategies/Signals/StcIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/StcIndicator.cs @@ -9,17 +9,17 @@ namespace Managing.Domain.Strategies.Signals; public class StcIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public StcIndicator(string name, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name, IndicatorType.Stc) { - Signals = new List(); + Signals = new List(); FastPeriods = fastPeriods; SlowPeriods = slowPeriods; CyclePeriods = cyclePeriods; } - public override List Run() + public override List Run() { if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods)) { @@ -103,7 +103,7 @@ public class StcIndicator : Indicator private void AddSignal(CandleSct candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal( + var signal = new LightSignal( MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, diff --git a/src/Managing.Domain/Strategies/Signals/SuperTrendCrossEma.cs b/src/Managing.Domain/Strategies/Signals/SuperTrendCrossEma.cs index 36ff15d..278c5e3 100644 --- a/src/Managing.Domain/Strategies/Signals/SuperTrendCrossEma.cs +++ b/src/Managing.Domain/Strategies/Signals/SuperTrendCrossEma.cs @@ -9,17 +9,17 @@ namespace Managing.Domain.Strategies.Signals; public class SuperTrendCrossEma : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public SuperTrendCrossEma(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrendCrossEma) { - Signals = new List(); + Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 100 + Period.Value; } - public override List Run() + public override List Run() { // Validate sufficient historical data for all indicators const int emaPeriod = 50; @@ -169,7 +169,7 @@ public class SuperTrendCrossEma : Indicator private void AddSignal(CandleSuperTrend candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) diff --git a/src/Managing.Domain/Strategies/Signals/SuperTrendIndicator.cs b/src/Managing.Domain/Strategies/Signals/SuperTrendIndicator.cs index f3fa69d..4b5015f 100644 --- a/src/Managing.Domain/Strategies/Signals/SuperTrendIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/SuperTrendIndicator.cs @@ -9,17 +9,17 @@ namespace Managing.Domain.Strategies.Signals; public class SuperTrendIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public SuperTrendIndicator(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrend) { - Signals = new List(); + Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 100 + Period.Value; } - public override List Run() + public override List Run() { if (Candles.Count <= MinimumHistory) { @@ -106,7 +106,7 @@ public class SuperTrendIndicator : Indicator private void AddSignal(CandleSuperTrend candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) diff --git a/src/Managing.Domain/Strategies/Signals/ThreeWhiteSoldiersIndicator.cs b/src/Managing.Domain/Strategies/Signals/ThreeWhiteSoldiersIndicator.cs index f13822a..0440255 100644 --- a/src/Managing.Domain/Strategies/Signals/ThreeWhiteSoldiersIndicator.cs +++ b/src/Managing.Domain/Strategies/Signals/ThreeWhiteSoldiersIndicator.cs @@ -16,9 +16,9 @@ namespace Managing.Domain.Strategies.Signals public TradeDirection Direction { get; } - public override List Run() + public override List Run() { - var signals = new List(); + var signals = new List(); if (Candles.Count <= 3) { diff --git a/src/Managing.Domain/Strategies/Trends/EmaTrendIndicator.cs b/src/Managing.Domain/Strategies/Trends/EmaTrendIndicator.cs index c5c3063..f3446c1 100644 --- a/src/Managing.Domain/Strategies/Trends/EmaTrendIndicator.cs +++ b/src/Managing.Domain/Strategies/Trends/EmaTrendIndicator.cs @@ -8,15 +8,15 @@ namespace Managing.Domain.Strategies.Trends; public class EmaTrendIndicator : EmaBaseIndicator { - public List Signals { get; set; } + public List Signals { get; set; } public EmaTrendIndicator(string name, int period) : base(name, IndicatorType.EmaTrend) { - Signals = new List(); + Signals = new List(); Period = period; } - public override List Run() + public override List Run() { if (Candles.Count <= 2 * Period) { @@ -64,7 +64,7 @@ public class EmaTrendIndicator : EmaBaseIndicator public void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, + var signal = new LightSignal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { diff --git a/src/Managing.Domain/Strategies/Trends/StochRsiTrendIndicator.cs b/src/Managing.Domain/Strategies/Trends/StochRsiTrendIndicator.cs index d8468a3..00a51bb 100644 --- a/src/Managing.Domain/Strategies/Trends/StochRsiTrendIndicator.cs +++ b/src/Managing.Domain/Strategies/Trends/StochRsiTrendIndicator.cs @@ -9,7 +9,7 @@ namespace Managing.Domain.Strategies.Trends; public class StochRsiTrendIndicator : Indicator { - public List Signals { get; set; } + public List Signals { get; set; } public StochRsiTrendIndicator( string name, @@ -18,14 +18,14 @@ public class StochRsiTrendIndicator : Indicator int signalPeriod, int smoothPeriods) : base(name, IndicatorType.StochRsiTrend) { - Signals = new List(); + Signals = new List(); StochPeriods = stochPeriod; SignalPeriods = signalPeriod; SmoothPeriods = smoothPeriods; Period = period; } - public override List Run() + public override List Run() { if (Candles.Count <= 10 * Period + 50) { @@ -100,7 +100,7 @@ public class StochRsiTrendIndicator : Indicator private void AddSignal(CandleStochRsi candleSignal, TradeDirection direction, Confidence confidence) { - var signal = new Signal( + var signal = new LightSignal( MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, diff --git a/src/Managing.Domain/Trades/Position.cs b/src/Managing.Domain/Trades/Position.cs index b915379..cc7f855 100644 --- a/src/Managing.Domain/Trades/Position.cs +++ b/src/Managing.Domain/Trades/Position.cs @@ -1,5 +1,5 @@ using System.ComponentModel.DataAnnotations; -using Managing.Domain.MoneyManagements; +using System.Text.Json.Serialization; using Managing.Domain.Users; using static Managing.Common.Enums; @@ -8,29 +8,37 @@ namespace Managing.Domain.Trades public class Position { public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker, - MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date, User user) + LightMoneyManagement moneyManagement, PositionInitiator initiator, DateTime date, User user) { Identifier = identifier; AccountName = accountName; OriginDirection = originDirection; Ticker = ticker; MoneyManagement = moneyManagement; - Initiator = positionInitiator; + Initiator = initiator; Date = date; Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New; User = user; } - [Required] public string AccountName { get; } + [Required] public string AccountName { get; set; } [Required] public DateTime Date { get; set; } - [Required] public TradeDirection OriginDirection { get; } - [Required] public Ticker Ticker { get; } - [Required] public MoneyManagement MoneyManagement { get; } - [Required] public Trade Open { get; set; } - [Required] public Trade StopLoss { get; set; } - [Required] public Trade TakeProfit1 { get; set; } - public Trade TakeProfit2 { get; set; } - public ProfitAndLoss ProfitAndLoss { get; set; } + [Required] public TradeDirection OriginDirection { get; set; } + [Required] public Ticker Ticker { get; set; } + [Required] public LightMoneyManagement MoneyManagement { get; set; } + [Required] [JsonPropertyName("Open")] public Trade Open { get; set; } + + [Required] + [JsonPropertyName("StopLoss")] + public Trade StopLoss { get; set; } + + [Required] + [JsonPropertyName("TakeProfit1")] + public Trade TakeProfit1 { get; set; } + + [JsonPropertyName("TakeProfit2")] public Trade TakeProfit2 { get; set; } + + [JsonPropertyName("ProfitAndLoss")] public ProfitAndLoss ProfitAndLoss { get; set; } [Required] public PositionStatus Status { get; set; } public string SignalIdentifier { get; set; } [Required] public string Identifier { get; set; } diff --git a/src/Managing.Domain/Trades/Trade.cs b/src/Managing.Domain/Trades/Trade.cs index 0917d5f..ebed50f 100644 --- a/src/Managing.Domain/Trades/Trade.cs +++ b/src/Managing.Domain/Trades/Trade.cs @@ -5,7 +5,7 @@ namespace Managing.Domain.Trades { public class Trade { - public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker, + public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker, decimal quantity, decimal price, decimal? leverage, string exchangeOrderId, string message) { Date = date; @@ -21,25 +21,17 @@ namespace Managing.Domain.Trades Fee = 0; } - public decimal Fee { get; set; } - [Required] - public DateTime Date { get; private set; } - [Required] - public TradeDirection Direction { get; } - [Required] - public TradeStatus Status { get; private set; } - [Required] - public TradeType TradeType { get; } - [Required] - public Ticker Ticker { get; } - [Required] - public decimal Quantity { get; set; } - [Required] - public decimal Price { get; set; } - public decimal Leverage { get; } - [Required] - public string ExchangeOrderId { get; private set; } - public string Message { get; private set; } + [Required] public decimal Fee { get; set; } + [Required] public DateTime Date { get; set; } + [Required] public TradeDirection Direction { get; set; } + [Required] public TradeStatus Status { get; set; } + [Required] public TradeType TradeType { get; set; } + [Required] public Ticker Ticker { get; set; } + [Required] public decimal Quantity { get; set; } + [Required] public decimal Price { get; set; } + [Required] public decimal Leverage { get; set; } + [Required] public string ExchangeOrderId { get; set; } + [Required] public string Message { get; set; } public void SetStatus(TradeStatus status) { @@ -71,4 +63,4 @@ namespace Managing.Domain.Trades Price = Math.Round(price, precision); } } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/AccountRepository.cs b/src/Managing.Infrastructure.Database/AccountRepository.cs deleted file mode 100644 index d76a5b2..0000000 --- a/src/Managing.Infrastructure.Database/AccountRepository.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Accounts; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class AccountRepository : IAccountRepository -{ - private readonly IMongoRepository _accountRepository; - - public AccountRepository(IMongoRepository accountRepository) - { - _accountRepository = accountRepository; - } - - public void DeleteAccountByName(string name) - { - var account = _accountRepository.FindOne(a => a.Name == name); - _accountRepository.DeleteById(account.Id.ToString()); - } - - public async Task GetAccountByKeyAsync(string key) - { - var account = await _accountRepository.FindOneAsync(a => a.Key == key); - return MongoMappers.Map(account); - } - - public async Task GetAccountByNameAsync(string name) - { - var account = await _accountRepository.FindOneAsync(a => a.Name == name); - return MongoMappers.Map(account); - } - - public IEnumerable GetAccounts() - { - var accounts = _accountRepository.FindAll(); - return MongoMappers.Map(accounts); - } - - public async Task InsertAccountAsync(Account account) - { - await _accountRepository.InsertOneAsync(MongoMappers.Map(account)); - } -} diff --git a/src/Managing.Infrastructure.Database/BacktestRepository.cs b/src/Managing.Infrastructure.Database/BacktestRepository.cs deleted file mode 100644 index cec4cd6..0000000 --- a/src/Managing.Infrastructure.Database/BacktestRepository.cs +++ /dev/null @@ -1,388 +0,0 @@ -using System.Diagnostics; -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Backtests; -using Managing.Domain.Users; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; -using MongoDB.Driver; - -namespace Managing.Infrastructure.Databases; - -public class BacktestRepository : IBacktestRepository -{ - private readonly IMongoRepository _backtestRepository; - private readonly IMongoRepository _bundleBacktestRepository; - - public BacktestRepository( - IMongoRepository backtestRepository, - IMongoRepository bundleBacktestRepository) - { - _backtestRepository = backtestRepository; - _bundleBacktestRepository = bundleBacktestRepository; - } - - // User-specific operations - public void InsertBacktestForUser(User user, Backtest result) - { - ValidateBacktestData(result); - result.User = user; - var dto = MongoMappers.Map(result); - _backtestRepository.InsertOne(dto); - } - - /// - /// Validates that all numeric fields in the backtest are of the correct type - /// - private void ValidateBacktestData(Backtest backtest) - { - // Ensure FinalPnl is a valid decimal - if (backtest.FinalPnl.GetType() != typeof(decimal)) - { - throw new InvalidOperationException( - $"FinalPnl must be of type decimal, but got {backtest.FinalPnl.GetType().Name}"); - } - - // Ensure other numeric fields are correct - if (backtest.GrowthPercentage.GetType() != typeof(decimal)) - { - throw new InvalidOperationException( - $"GrowthPercentage must be of type decimal, but got {backtest.GrowthPercentage.GetType().Name}"); - } - - if (backtest.HodlPercentage.GetType() != typeof(decimal)) - { - throw new InvalidOperationException( - $"HodlPercentage must be of type decimal, but got {backtest.HodlPercentage.GetType().Name}"); - } - - if (backtest.Score.GetType() != typeof(double)) - { - throw new InvalidOperationException( - $"Score must be of type double, but got {backtest.Score.GetType().Name}"); - } - - if (backtest.WinRate.GetType() != typeof(int)) - { - throw new InvalidOperationException( - $"WinRate must be of type int, but got {backtest.WinRate.GetType().Name}"); - } - } - - public IEnumerable GetBacktestsByUser(User user) - { - var backtests = _backtestRepository.AsQueryable() - .Where(b => b.User.Name == user.Name) - .ToList(); - - return backtests.Select(b => MongoMappers.Map(b)); - } - - public IEnumerable GetBacktestsByRequestId(string requestId) - { - var backtests = _backtestRepository.AsQueryable() - .Where(b => b.RequestId == requestId) - .ToList(); - - return backtests.Select(b => MongoMappers.Map(b)); - } - - public (IEnumerable Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, - int page, int pageSize, string sortBy = "score", string sortOrder = "desc") - { - var stopwatch = Stopwatch.StartNew(); - var collection = _backtestRepository.GetCollection(); // You may need to expose this in your repo - - var filter = Builders.Filter.Eq(b => b.RequestId, requestId); - - var afterQueryMs = stopwatch.ElapsedMilliseconds; - var totalCount = collection.CountDocuments(filter); - var afterCountMs = stopwatch.ElapsedMilliseconds; - - var projection = Builders.Projection - .Include(b => b.Identifier) - .Include(b => b.FinalPnl) - .Include(b => b.WinRate) - .Include(b => b.GrowthPercentage) - .Include(b => b.HodlPercentage) - .Include(b => b.StartDate) - .Include(b => b.EndDate) - .Include(b => b.Score) - .Include(b => b.ScoreMessage) - .Include(b => b.Config) - .Include(b => b.Fees) - .Include(b => b.Statistics); - - // Build sort definition - var sortDefinition = sortBy.ToLower() switch - { - "score" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.Score) - : Builders.Sort.Ascending(b => b.Score), - "finalpnl" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.FinalPnl) - : Builders.Sort.Ascending(b => b.FinalPnl), - "winrate" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.WinRate) - : Builders.Sort.Ascending(b => b.WinRate), - "growthpercentage" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.GrowthPercentage) - : Builders.Sort.Ascending(b => b.GrowthPercentage), - "hodlpercentage" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.HodlPercentage) - : Builders.Sort.Ascending(b => b.HodlPercentage), - _ => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.Score) - : Builders.Sort.Ascending(b => b.Score) - }; - - var afterProjectionMs = stopwatch.ElapsedMilliseconds; - var backtests = collection - .Find(filter) - .Project(projection) - .Sort(sortDefinition) - .Skip((page - 1) * pageSize) - .Limit(pageSize) - .ToList(); - var afterToListMs = stopwatch.ElapsedMilliseconds; - - Console.WriteLine( - $"[BacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms"); - - var mappedBacktests = backtests.Select(b => new LightBacktest - { - Id = b.Identifier, - Config = MongoMappers.Map(b.Config), - FinalPnl = b.FinalPnl, - WinRate = b.WinRate, - GrowthPercentage = b.GrowthPercentage, - HodlPercentage = b.HodlPercentage, - StartDate = b.StartDate, - EndDate = b.EndDate, - MaxDrawdown = b.Statistics?.MaxDrawdown, - Fees = b.Fees, - SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null, - Score = b.Score, - ScoreMessage = b.ScoreMessage ?? string.Empty - }); - - return (mappedBacktests, (int)totalCount); - } - - public Backtest GetBacktestByIdForUser(User user, string id) - { - var backtest = _backtestRepository.FindOne(b => b.Identifier == id); - - // Check if backtest exists and belongs to the user - if (backtest != null && backtest.User != null && backtest.User.Name == user.Name) - { - return MongoMappers.Map(backtest); - } - - return null; - } - - public void DeleteBacktestByIdForUser(User user, string id) - { - var backtest = _backtestRepository.FindOne(b => b.Identifier == id); - - if (backtest != null && backtest.User != null && backtest.User.Name == user.Name) - { - _backtestRepository.DeleteById(backtest.Id.ToString()); - } - } - - public void DeleteBacktestsByIdsForUser(User user, IEnumerable ids) - { - var backtests = _backtestRepository.AsQueryable() - .Where(b => b.User != null && b.User.Name == user.Name && ids.Contains(b.Identifier)) - .ToList(); - - foreach (var backtest in backtests) - { - _backtestRepository.DeleteById(backtest.Id.ToString()); - } - } - - public void DeleteAllBacktestsForUser(User user) - { - var backtests = _backtestRepository.AsQueryable() - .Where(b => b.User != null && b.User.Name == user.Name) - .ToList(); - - foreach (var backtest in backtests) - { - _backtestRepository.DeleteById(backtest.Id.ToString()); - } - } - - public void DeleteBacktestsByRequestId(string requestId) - { - var backtests = _backtestRepository.AsQueryable() - .Where(b => b.RequestId == requestId) - .ToList(); - - foreach (var backtest in backtests) - { - _backtestRepository.DeleteById(backtest.Id.ToString()); - } - } - - public (IEnumerable Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, - int pageSize, string sortBy = "score", string sortOrder = "desc") - { - var stopwatch = Stopwatch.StartNew(); - var collection = _backtestRepository.GetCollection(); - - var filter = Builders.Filter.Eq(b => b.User.Name, user.Name); - - var afterQueryMs = stopwatch.ElapsedMilliseconds; - var totalCount = collection.CountDocuments(filter); - var afterCountMs = stopwatch.ElapsedMilliseconds; - - var projection = Builders.Projection - .Include(b => b.Identifier) - .Include(b => b.FinalPnl) - .Include(b => b.WinRate) - .Include(b => b.GrowthPercentage) - .Include(b => b.HodlPercentage) - .Include(b => b.StartDate) - .Include(b => b.EndDate) - .Include(b => b.Score) - .Include(b => b.ScoreMessage) - .Include(b => b.Config) - .Include(b => b.Fees) - .Include(b => b.Statistics); - - // Build sort definition - var sortDefinition = sortBy.ToLower() switch - { - "score" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.Score) - : Builders.Sort.Ascending(b => b.Score), - "finalpnl" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.FinalPnl) - : Builders.Sort.Ascending(b => b.FinalPnl), - "winrate" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.WinRate) - : Builders.Sort.Ascending(b => b.WinRate), - "growthpercentage" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.GrowthPercentage) - : Builders.Sort.Ascending(b => b.GrowthPercentage), - "hodlpercentage" => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.HodlPercentage) - : Builders.Sort.Ascending(b => b.HodlPercentage), - _ => sortOrder == "desc" - ? Builders.Sort.Descending(b => b.Score) - : Builders.Sort.Ascending(b => b.Score) - }; - - var afterProjectionMs = stopwatch.ElapsedMilliseconds; - var backtests = collection - .Find(filter) - .Project(projection) - .Sort(sortDefinition) - .Skip((page - 1) * pageSize) - .Limit(pageSize) - .ToList(); - var afterToListMs = stopwatch.ElapsedMilliseconds; - - Console.WriteLine( - $"[BacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms"); - - var mappedBacktests = backtests.Select(b => new LightBacktest - { - Id = b.Identifier, - Config = MongoMappers.Map(b.Config), - FinalPnl = b.FinalPnl, - WinRate = b.WinRate, - GrowthPercentage = b.GrowthPercentage, - HodlPercentage = b.HodlPercentage, - StartDate = b.StartDate, - EndDate = b.EndDate, - MaxDrawdown = b.Statistics?.MaxDrawdown, - Fees = b.Fees, - SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null, - Score = b.Score, - ScoreMessage = b.ScoreMessage ?? string.Empty - }); - - return (mappedBacktests, (int)totalCount); - } - - // Bundle backtest methods - public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest) - { - bundleRequest.User = user; - var dto = MongoMappers.Map(bundleRequest); - _bundleBacktestRepository.InsertOne(dto); - } - - public IEnumerable GetBundleBacktestRequestsByUser(User user) - { - var projection = Builders.Projection - .Include(b => b.RequestId) - .Include(b => b.Status) - .Include(b => b.CreatedAt) - .Include(b => b.CurrentBacktest) - .Include(b => b.EstimatedTimeRemainingSeconds) - .Include(b => b.TotalBacktests) - .Include(b => b.CurrentBacktest) - .Include(b => b.CompletedAt) - .Include(b => b.ErrorMessage) - .Include(b => b.ProgressInfo) - .Include(b => b.Name) - .Include(b => b.CompletedBacktests) - .Include(b => b.User); - - var filter = Builders.Filter.Eq(b => b.User.Name, user.Name); - var bundleRequests = _bundleBacktestRepository.GetCollection() - .Find(filter) - .Project(projection) - .ToList(); - - return bundleRequests.Select(MongoMappers.Map); - } - - public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id) - { - var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id && b.User.Name == user.Name); - - if (bundleRequest != null && bundleRequest.User.Name == user.Name) - { - return MongoMappers.Map(bundleRequest); - } - - return null; - } - - public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest) - { - var existingRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == bundleRequest.RequestId); - if (existingRequest != null) - { - var dto = MongoMappers.Map(bundleRequest); - dto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId - _bundleBacktestRepository.ReplaceOne(dto); - } - } - - public void DeleteBundleBacktestRequestByIdForUser(User user, string id) - { - var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id); - - if (bundleRequest != null && bundleRequest.User.Name == user.Name) - { - _bundleBacktestRepository.DeleteById(bundleRequest.Id.ToString()); - } - } - - public IEnumerable GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status) - { - var requests = _bundleBacktestRepository.AsQueryable() - .Where(b => b.Status == status) - .ToList(); - - return requests.Select(MongoMappers.Map); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/BotRepository.cs b/src/Managing.Infrastructure.Database/BotRepository.cs deleted file mode 100644 index 32bd438..0000000 --- a/src/Managing.Infrastructure.Database/BotRepository.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Bots; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class BotRepository : IBotRepository -{ - private readonly IMongoRepository _botRepository; - - public BotRepository(IMongoRepository botRepository) - { - _botRepository = botRepository; - } - - public async Task InsertBotAsync(BotBackup bot) - { - await _botRepository.InsertOneAsync(MongoMappers.Map(bot)); - } - - public IEnumerable GetBots() - { - var bots = _botRepository.FindAll(); - return bots.Select(b => MongoMappers.Map(b)); - } - - public async Task UpdateBackupBot(BotBackup bot) - { - var b = await _botRepository.FindOneAsync(b => b.Identifier == bot.Identifier); - var dto = MongoMappers.Map(bot); - dto.Id = b.Id; - _botRepository.Update(dto); - } - - public async Task DeleteBotBackup(string identifier) - { - var backup = await _botRepository.FindOneAsync(b => b.Identifier == identifier); - await _botRepository.DeleteOneAsync(b => b.Id == backup.Id); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/GeneticRepository.cs b/src/Managing.Infrastructure.Database/GeneticRepository.cs deleted file mode 100644 index 49c9a73..0000000 --- a/src/Managing.Infrastructure.Database/GeneticRepository.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Backtests; -using Managing.Domain.Users; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class GeneticRepository : IGeneticRepository -{ - private readonly IMongoRepository _geneticRequestRepository; - - public GeneticRepository(IMongoRepository geneticRequestRepository) - { - _geneticRequestRepository = geneticRequestRepository; - } - - public void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest) - { - geneticRequest.User = user; - _geneticRequestRepository.InsertOne(MongoMappers.Map(geneticRequest)); - } - - public IEnumerable GetGeneticRequestsByUser(User user) - { - var geneticRequests = _geneticRequestRepository.AsQueryable() - .Where(gr => gr.User.Name == user.Name) - .OrderByDescending(gr => gr.CreatedAt) - .ToList(); - - return geneticRequests.Select(gr => MongoMappers.Map(gr)); - } - - public GeneticRequest GetGeneticRequestByIdForUser(User user, string id) - { - var geneticRequest = _geneticRequestRepository.FindById(id); - - // Check if genetic request exists and belongs to the user - if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name) - { - return MongoMappers.Map(geneticRequest); - } - - return null; - } - - public void UpdateGeneticRequest(GeneticRequest geneticRequest) - { - var existingRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == geneticRequest.RequestId); - if (existingRequest != null) - { - var updatedDto = MongoMappers.Map(geneticRequest); - updatedDto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId - _geneticRequestRepository.ReplaceOne(updatedDto); - } - } - - public void DeleteGeneticRequestByIdForUser(User user, string id) - { - var geneticRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == id); - - if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name) - { - _geneticRequestRepository.DeleteById(geneticRequest.Id.ToString()); - } - } - - public void DeleteAllGeneticRequestsForUser(User user) - { - var geneticRequests = _geneticRequestRepository.AsQueryable() - .Where(gr => gr.User != null && gr.User.Name == user.Name) - .ToList(); - - foreach (var geneticRequest in geneticRequests) - { - _geneticRequestRepository.DeleteById(geneticRequest.Id.ToString()); - } - } - - public IEnumerable GetPendingGeneticRequests() - { - var pendingRequests = _geneticRequestRepository.AsQueryable() - .Where(gr => gr.Status == "Pending") - .OrderBy(gr => gr.CreatedAt) - .ToList(); - - return pendingRequests.Select(gr => MongoMappers.Map(gr)); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/AgentBalanceRepository.cs b/src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs similarity index 100% rename from src/Managing.Infrastructure.Database/AgentBalanceRepository.cs rename to src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs diff --git a/src/Managing.Infrastructure.Database/CandleRepository.cs b/src/Managing.Infrastructure.Database/InfluxDb/CandleRepository.cs similarity index 100% rename from src/Managing.Infrastructure.Database/CandleRepository.cs rename to src/Managing.Infrastructure.Database/InfluxDb/CandleRepository.cs diff --git a/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs b/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs index ef962b5..3fa8e61 100644 --- a/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs +++ b/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs @@ -19,11 +19,6 @@ public static class PriceHelpers CloseTime = candle.Date, High = candle.High, Low = candle.Low, - BaseVolume = candle.BaseVolume, - QuoteVolume = candle.QuoteVolume, - TradeCount = candle.TradeCount, - TakerBuyBaseVolume = candle.TakerBuyBaseVolume, - TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, Timeframe = candle.Timeframe.ToString() }; @@ -42,11 +37,6 @@ public static class PriceHelpers Date = dto.CloseTime, High = dto.High, Low = dto.Low, - BaseVolume = dto.BaseVolume, - QuoteVolume = dto.QuoteVolume, - TradeCount = dto.TradeCount, - TakerBuyBaseVolume = dto.TakerBuyBaseVolume, - TakerBuyQuoteVolume = dto.TakerBuyQuoteVolume, Timeframe = MiscExtensions.ParseEnum(dto.Timeframe) }; } diff --git a/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj b/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj index 9a5be2b..7b03f75 100644 --- a/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj +++ b/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj @@ -10,11 +10,17 @@ - - + + + + + + + + diff --git a/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.Designer.cs new file mode 100644 index 0000000..70152d4 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.Designer.cs @@ -0,0 +1,1224 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250723194312_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.cs b/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.cs new file mode 100644 index 0000000..e216e6f --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250723194312_InitialCreate.cs @@ -0,0 +1,1011 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Backtests", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + RequestId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + FinalPnl = table.Column(type: "numeric(18,8)", nullable: false), + WinRate = table.Column(type: "integer", nullable: false), + GrowthPercentage = table.Column(type: "numeric(18,8)", nullable: false), + HodlPercentage = table.Column(type: "numeric(18,8)", nullable: false), + ConfigJson = table.Column(type: "jsonb", nullable: false), + PositionsJson = table.Column(type: "jsonb", nullable: false), + SignalsJson = table.Column(type: "jsonb", nullable: false), + StartDate = table.Column(type: "timestamp with time zone", nullable: false), + EndDate = table.Column(type: "timestamp with time zone", nullable: false), + MoneyManagementJson = table.Column(type: "jsonb", nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + StatisticsJson = table.Column(type: "jsonb", nullable: true), + Fees = table.Column(type: "numeric(18,8)", nullable: false), + Score = table.Column(type: "double precision", nullable: false), + ScoreMessage = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + Metadata = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Backtests", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BotBackups", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Data = table.Column(type: "jsonb", nullable: false), + LastStatus = table.Column(type: "integer", nullable: false), + CreateDate = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BotBackups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BundleBacktestRequests", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RequestId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CompletedAt = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "text", nullable: false), + BacktestRequestsJson = table.Column(type: "text", nullable: false), + TotalBacktests = table.Column(type: "integer", nullable: false), + CompletedBacktests = table.Column(type: "integer", nullable: false), + FailedBacktests = table.Column(type: "integer", nullable: false), + ErrorMessage = table.Column(type: "text", nullable: true), + ProgressInfo = table.Column(type: "text", nullable: true), + CurrentBacktest = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + EstimatedTimeRemainingSeconds = table.Column(type: "integer", nullable: true), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + ResultsJson = table.Column(type: "jsonb", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BundleBacktestRequests", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Fees", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Cost = table.Column(type: "numeric(18,8)", nullable: false), + Exchange = table.Column(type: "text", nullable: false), + LastUpdate = table.Column(type: "timestamp with time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Fees", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "FundingRates", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Ticker = table.Column(type: "integer", nullable: false), + Exchange = table.Column(type: "integer", nullable: false), + Rate = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + OpenInterest = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + Direction = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FundingRates", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Indicators", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Type = table.Column(type: "text", nullable: false), + Timeframe = table.Column(type: "text", nullable: false), + SignalType = table.Column(type: "text", nullable: false), + MinimumHistory = table.Column(type: "integer", nullable: false), + Period = table.Column(type: "integer", nullable: true), + FastPeriods = table.Column(type: "integer", nullable: true), + SlowPeriods = table.Column(type: "integer", nullable: true), + SignalPeriods = table.Column(type: "integer", nullable: true), + Multiplier = table.Column(type: "double precision", nullable: true), + StochPeriods = table.Column(type: "integer", nullable: true), + SmoothPeriods = table.Column(type: "integer", nullable: true), + CyclePeriods = table.Column(type: "integer", nullable: true), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Indicators", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MoneyManagements", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Timeframe = table.Column(type: "text", nullable: false), + StopLoss = table.Column(type: "numeric(18,8)", nullable: false), + TakeProfit = table.Column(type: "numeric(18,8)", nullable: false), + Leverage = table.Column(type: "numeric(18,8)", nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MoneyManagements", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Scenarios", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + LoopbackPeriod = table.Column(type: "integer", nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Scenarios", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Signals", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Direction = table.Column(type: "text", nullable: false), + Confidence = table.Column(type: "text", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + Ticker = table.Column(type: "text", nullable: false), + Status = table.Column(type: "text", nullable: false), + Timeframe = table.Column(type: "text", nullable: false), + Type = table.Column(type: "text", nullable: false), + SignalType = table.Column(type: "text", nullable: false), + IndicatorName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CandleJson = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Signals", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SpotlightOverviews", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "uuid", nullable: false), + DateTime = table.Column(type: "timestamp with time zone", nullable: false), + ScenarioCount = table.Column(type: "integer", nullable: false), + SpotlightsJson = table.Column(type: "jsonb", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SpotlightOverviews", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TopVolumeTickers", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Ticker = table.Column(type: "integer", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + Volume = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + Rank = table.Column(type: "integer", nullable: false), + Exchange = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TopVolumeTickers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Traders", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Address = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Winrate = table.Column(type: "integer", nullable: false), + Pnl = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + TradeCount = table.Column(type: "integer", nullable: false), + AverageWin = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + AverageLoss = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + Roi = table.Column(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false), + IsBestTrader = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Traders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Trades", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Date = table.Column(type: "timestamp with time zone", nullable: false), + Direction = table.Column(type: "text", nullable: false), + Status = table.Column(type: "text", nullable: false), + TradeType = table.Column(type: "text", nullable: false), + Ticker = table.Column(type: "text", nullable: false), + Fee = table.Column(type: "numeric(18,8)", nullable: false), + Quantity = table.Column(type: "numeric(18,8)", nullable: false), + Price = table.Column(type: "numeric(18,8)", nullable: false), + Leverage = table.Column(type: "numeric(18,8)", nullable: false), + ExchangeOrderId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Message = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Trades", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + AgentName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + AvatarUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + TelegramChannel = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ScenarioIndicators", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScenarioId = table.Column(type: "integer", nullable: false), + IndicatorId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScenarioIndicators", x => x.Id); + table.ForeignKey( + name: "FK_ScenarioIndicators_Indicators_IndicatorId", + column: x => x.IndicatorId, + principalTable: "Indicators", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScenarioIndicators_Scenarios_ScenarioId", + column: x => x.ScenarioId, + principalTable: "Scenarios", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Positions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + ProfitAndLoss = table.Column(type: "numeric(18,8)", nullable: false), + OriginDirection = table.Column(type: "text", nullable: false), + Status = table.Column(type: "text", nullable: false), + Ticker = table.Column(type: "text", nullable: false), + Initiator = table.Column(type: "text", nullable: false), + SignalIdentifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + AccountName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + UserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + OpenTradeId = table.Column(type: "integer", nullable: true), + StopLossTradeId = table.Column(type: "integer", nullable: true), + TakeProfit1TradeId = table.Column(type: "integer", nullable: true), + TakeProfit2TradeId = table.Column(type: "integer", nullable: true), + MoneyManagementJson = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Positions", x => x.Id); + table.ForeignKey( + name: "FK_Positions_Trades_OpenTradeId", + column: x => x.OpenTradeId, + principalTable: "Trades", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Positions_Trades_StopLossTradeId", + column: x => x.StopLossTradeId, + principalTable: "Trades", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Positions_Trades_TakeProfit1TradeId", + column: x => x.TakeProfit1TradeId, + principalTable: "Trades", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Positions_Trades_TakeProfit2TradeId", + column: x => x.TakeProfit2TradeId, + principalTable: "Trades", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "Accounts", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Exchange = table.Column(type: "text", nullable: false), + Type = table.Column(type: "text", nullable: false), + Key = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Secret = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + UserId = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Accounts", x => x.Id); + table.ForeignKey( + name: "FK_Accounts_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "GeneticRequests", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RequestId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + UserId = table.Column(type: "integer", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CompletedAt = table.Column(type: "timestamp with time zone", nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Ticker = table.Column(type: "text", nullable: false), + Timeframe = table.Column(type: "text", nullable: false), + StartDate = table.Column(type: "timestamp with time zone", nullable: false), + EndDate = table.Column(type: "timestamp with time zone", nullable: false), + Balance = table.Column(type: "numeric(18,8)", nullable: false), + PopulationSize = table.Column(type: "integer", nullable: false), + Generations = table.Column(type: "integer", nullable: false), + MutationRate = table.Column(type: "double precision", nullable: false), + SelectionMethod = table.Column(type: "text", nullable: false), + CrossoverMethod = table.Column(type: "text", nullable: false), + MutationMethod = table.Column(type: "text", nullable: false), + ElitismPercentage = table.Column(type: "integer", nullable: false), + MaxTakeProfit = table.Column(type: "double precision", nullable: false), + EligibleIndicatorsJson = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + BestFitness = table.Column(type: "double precision", nullable: true), + BestIndividual = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: true), + ErrorMessage = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + ProgressInfo = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: true), + BestChromosome = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: true), + BestFitnessSoFar = table.Column(type: "double precision", nullable: true), + CurrentGeneration = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GeneticRequests", x => x.Id); + table.ForeignKey( + name: "FK_GeneticRequests_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_Key", + table: "Accounts", + column: "Key"); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_Name", + table: "Accounts", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_UserId", + table: "Accounts", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_CreatedAt", + table: "Backtests", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_EndDate", + table: "Backtests", + column: "EndDate"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_FinalPnl", + table: "Backtests", + column: "FinalPnl"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_Identifier", + table: "Backtests", + column: "Identifier", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_RequestId", + table: "Backtests", + column: "RequestId"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_RequestId_Score", + table: "Backtests", + columns: new[] { "RequestId", "Score" }); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_Score", + table: "Backtests", + column: "Score"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_StartDate", + table: "Backtests", + column: "StartDate"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_UserName", + table: "Backtests", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_UserName_Score", + table: "Backtests", + columns: new[] { "UserName", "Score" }); + + migrationBuilder.CreateIndex( + name: "IX_Backtests_WinRate", + table: "Backtests", + column: "WinRate"); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_CreateDate", + table: "BotBackups", + column: "CreateDate"); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_Identifier", + table: "BotBackups", + column: "Identifier", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_LastStatus", + table: "BotBackups", + column: "LastStatus"); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_UserName", + table: "BotBackups", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_UserName_CreateDate", + table: "BotBackups", + columns: new[] { "UserName", "CreateDate" }); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_CompletedAt", + table: "BundleBacktestRequests", + column: "CompletedAt"); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_CreatedAt", + table: "BundleBacktestRequests", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_RequestId", + table: "BundleBacktestRequests", + column: "RequestId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_Status", + table: "BundleBacktestRequests", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_UserName", + table: "BundleBacktestRequests", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_UserName_CreatedAt", + table: "BundleBacktestRequests", + columns: new[] { "UserName", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_Fees_Exchange", + table: "Fees", + column: "Exchange", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Fees_LastUpdate", + table: "Fees", + column: "LastUpdate"); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Date", + table: "FundingRates", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Direction", + table: "FundingRates", + column: "Direction"); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Exchange", + table: "FundingRates", + column: "Exchange"); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Exchange_Date", + table: "FundingRates", + columns: new[] { "Exchange", "Date" }); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Ticker", + table: "FundingRates", + column: "Ticker"); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Ticker_Exchange", + table: "FundingRates", + columns: new[] { "Ticker", "Exchange" }); + + migrationBuilder.CreateIndex( + name: "IX_FundingRates_Ticker_Exchange_Date", + table: "FundingRates", + columns: new[] { "Ticker", "Exchange", "Date" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GeneticRequests_CreatedAt", + table: "GeneticRequests", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_GeneticRequests_RequestId", + table: "GeneticRequests", + column: "RequestId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GeneticRequests_Status", + table: "GeneticRequests", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_GeneticRequests_UserId", + table: "GeneticRequests", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Indicators_CreatedAt", + table: "Indicators", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Indicators_Name", + table: "Indicators", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Indicators_Type", + table: "Indicators", + column: "Type"); + + migrationBuilder.CreateIndex( + name: "IX_Indicators_UserName", + table: "Indicators", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_Indicators_UserName_Name", + table: "Indicators", + columns: new[] { "UserName", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_MoneyManagements_Name", + table: "MoneyManagements", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_MoneyManagements_UserName", + table: "MoneyManagements", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_MoneyManagements_UserName_Name", + table: "MoneyManagements", + columns: new[] { "UserName", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_Positions_CreatedAt", + table: "Positions", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_Date", + table: "Positions", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_Identifier", + table: "Positions", + column: "Identifier", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Positions_Initiator", + table: "Positions", + column: "Initiator"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_OpenTradeId", + table: "Positions", + column: "OpenTradeId"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_Status", + table: "Positions", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_StopLossTradeId", + table: "Positions", + column: "StopLossTradeId"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_TakeProfit1TradeId", + table: "Positions", + column: "TakeProfit1TradeId"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_TakeProfit2TradeId", + table: "Positions", + column: "TakeProfit2TradeId"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_UserName", + table: "Positions", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_Positions_UserName_Identifier", + table: "Positions", + columns: new[] { "UserName", "Identifier" }); + + migrationBuilder.CreateIndex( + name: "IX_ScenarioIndicators_IndicatorId", + table: "ScenarioIndicators", + column: "IndicatorId"); + + migrationBuilder.CreateIndex( + name: "IX_ScenarioIndicators_ScenarioId", + table: "ScenarioIndicators", + column: "ScenarioId"); + + migrationBuilder.CreateIndex( + name: "IX_ScenarioIndicators_ScenarioId_IndicatorId", + table: "ScenarioIndicators", + columns: new[] { "ScenarioId", "IndicatorId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Scenarios_CreatedAt", + table: "Scenarios", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Scenarios_Name", + table: "Scenarios", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Scenarios_UserName", + table: "Scenarios", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_Scenarios_UserName_Name", + table: "Scenarios", + columns: new[] { "UserName", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_Signals_CreatedAt", + table: "Signals", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_Date", + table: "Signals", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_Identifier", + table: "Signals", + column: "Identifier"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_Identifier_Date_UserName", + table: "Signals", + columns: new[] { "Identifier", "Date", "UserName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Signals_Status", + table: "Signals", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_Ticker", + table: "Signals", + column: "Ticker"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_UserName", + table: "Signals", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_Signals_UserName_Date", + table: "Signals", + columns: new[] { "UserName", "Date" }); + + migrationBuilder.CreateIndex( + name: "IX_SpotlightOverviews_DateTime", + table: "SpotlightOverviews", + column: "DateTime"); + + migrationBuilder.CreateIndex( + name: "IX_SpotlightOverviews_DateTime_ScenarioCount", + table: "SpotlightOverviews", + columns: new[] { "DateTime", "ScenarioCount" }); + + migrationBuilder.CreateIndex( + name: "IX_SpotlightOverviews_Identifier", + table: "SpotlightOverviews", + column: "Identifier", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpotlightOverviews_ScenarioCount", + table: "SpotlightOverviews", + column: "ScenarioCount"); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Date", + table: "TopVolumeTickers", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Date_Rank", + table: "TopVolumeTickers", + columns: new[] { "Date", "Rank" }); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Exchange", + table: "TopVolumeTickers", + column: "Exchange"); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Exchange_Date", + table: "TopVolumeTickers", + columns: new[] { "Exchange", "Date" }); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Rank", + table: "TopVolumeTickers", + column: "Rank"); + + migrationBuilder.CreateIndex( + name: "IX_TopVolumeTickers_Ticker", + table: "TopVolumeTickers", + column: "Ticker"); + + migrationBuilder.CreateIndex( + name: "IX_Traders_Address", + table: "Traders", + column: "Address"); + + migrationBuilder.CreateIndex( + name: "IX_Traders_Address_IsBestTrader", + table: "Traders", + columns: new[] { "Address", "IsBestTrader" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Traders_IsBestTrader", + table: "Traders", + column: "IsBestTrader"); + + migrationBuilder.CreateIndex( + name: "IX_Traders_IsBestTrader_Roi", + table: "Traders", + columns: new[] { "IsBestTrader", "Roi" }); + + migrationBuilder.CreateIndex( + name: "IX_Traders_IsBestTrader_Winrate", + table: "Traders", + columns: new[] { "IsBestTrader", "Winrate" }); + + migrationBuilder.CreateIndex( + name: "IX_Traders_Pnl", + table: "Traders", + column: "Pnl"); + + migrationBuilder.CreateIndex( + name: "IX_Traders_Roi", + table: "Traders", + column: "Roi"); + + migrationBuilder.CreateIndex( + name: "IX_Traders_Winrate", + table: "Traders", + column: "Winrate"); + + migrationBuilder.CreateIndex( + name: "IX_Trades_CreatedAt", + table: "Trades", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_Trades_Date", + table: "Trades", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_Trades_ExchangeOrderId", + table: "Trades", + column: "ExchangeOrderId"); + + migrationBuilder.CreateIndex( + name: "IX_Trades_Status", + table: "Trades", + column: "Status"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Accounts"); + + migrationBuilder.DropTable( + name: "Backtests"); + + migrationBuilder.DropTable( + name: "BotBackups"); + + migrationBuilder.DropTable( + name: "BundleBacktestRequests"); + + migrationBuilder.DropTable( + name: "Fees"); + + migrationBuilder.DropTable( + name: "FundingRates"); + + migrationBuilder.DropTable( + name: "GeneticRequests"); + + migrationBuilder.DropTable( + name: "MoneyManagements"); + + migrationBuilder.DropTable( + name: "Positions"); + + migrationBuilder.DropTable( + name: "ScenarioIndicators"); + + migrationBuilder.DropTable( + name: "Signals"); + + migrationBuilder.DropTable( + name: "SpotlightOverviews"); + + migrationBuilder.DropTable( + name: "TopVolumeTickers"); + + migrationBuilder.DropTable( + name: "Traders"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "Trades"); + + migrationBuilder.DropTable( + name: "Indicators"); + + migrationBuilder.DropTable( + name: "Scenarios"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.Designer.cs new file mode 100644 index 0000000..971118b --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.Designer.cs @@ -0,0 +1,1224 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250723221025_UpdateBotBackupDataToText")] + partial class UpdateBotBackupDataToText + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.cs b/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.cs new file mode 100644 index 0000000..f89ebfa --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250723221025_UpdateBotBackupDataToText.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class UpdateBotBackupDataToText : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Data", + table: "BotBackups", + type: "text", + nullable: false, + oldClrType: typeof(string), + oldType: "jsonb"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Data", + table: "BotBackups", + type: "jsonb", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.Designer.cs new file mode 100644 index 0000000..c4e11c9 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.Designer.cs @@ -0,0 +1,1259 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250724141819_AddWorkerEntity")] + partial class AddWorkerEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.cs b/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.cs new file mode 100644 index 0000000..a5208f7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724141819_AddWorkerEntity.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddWorkerEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Workers", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + WorkerType = table.Column(type: "text", nullable: false), + StartTime = table.Column(type: "timestamp with time zone", nullable: false), + LastRunTime = table.Column(type: "timestamp with time zone", nullable: true), + ExecutionCount = table.Column(type: "integer", nullable: false), + DelayTicks = table.Column(type: "bigint", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Workers", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Workers_WorkerType", + table: "Workers", + column: "WorkerType", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Workers"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.Designer.cs new file mode 100644 index 0000000..e3a4b4f --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.Designer.cs @@ -0,0 +1,1349 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250724160015_AddSynthEntities")] + partial class AddSynthEntities + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.cs b/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.cs new file mode 100644 index 0000000..3ded012 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724160015_AddSynthEntities.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddSynthEntities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SynthMinersLeaderboards", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Asset = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + TimeIncrement = table.Column(type: "integer", nullable: false), + SignalDate = table.Column(type: "timestamp with time zone", nullable: true), + IsBacktest = table.Column(type: "boolean", nullable: false), + MinersData = table.Column(type: "jsonb", nullable: false), + CacheKey = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SynthMinersLeaderboards", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SynthPredictions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Asset = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + MinerUid = table.Column(type: "integer", nullable: false), + TimeIncrement = table.Column(type: "integer", nullable: false), + TimeLength = table.Column(type: "integer", nullable: false), + SignalDate = table.Column(type: "timestamp with time zone", nullable: true), + IsBacktest = table.Column(type: "boolean", nullable: false), + PredictionData = table.Column(type: "jsonb", nullable: false), + CacheKey = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SynthPredictions", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_SynthMinersLeaderboards_CacheKey", + table: "SynthMinersLeaderboards", + column: "CacheKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SynthMinersLeaderboards_CreatedAt", + table: "SynthMinersLeaderboards", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_SynthPredictions_CacheKey", + table: "SynthPredictions", + column: "CacheKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SynthPredictions_CreatedAt", + table: "SynthPredictions", + column: "CreatedAt"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SynthMinersLeaderboards"); + + migrationBuilder.DropTable( + name: "SynthPredictions"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.Designer.cs new file mode 100644 index 0000000..f1152b7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.Designer.cs @@ -0,0 +1,1349 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250724164138_UpdateScoreMessageToText")] + partial class UpdateScoreMessageToText + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.cs b/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.cs new file mode 100644 index 0000000..f1fc77c --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250724164138_UpdateScoreMessageToText.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class UpdateScoreMessageToText : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ScoreMessage", + table: "Backtests", + type: "text", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(1000)", + oldMaxLength: 1000); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ScoreMessage", + table: "Backtests", + type: "character varying(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "text", + oldMaxLength: 1000); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.Designer.cs new file mode 100644 index 0000000..d5b1b72 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.Designer.cs @@ -0,0 +1,1364 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250725014014_AddUserIdToBundleBacktestRequest")] + partial class AddUserIdToBundleBacktestRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("OptimizedMoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.cs b/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.cs new file mode 100644 index 0000000..da200c1 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725014014_AddUserIdToBundleBacktestRequest.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddUserIdToBundleBacktestRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "BundleBacktestRequests", + type: "integer", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_BundleBacktestRequests_UserId", + table: "BundleBacktestRequests", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_BundleBacktestRequests_Users_UserId", + table: "BundleBacktestRequests", + column: "UserId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_BundleBacktestRequests_Users_UserId", + table: "BundleBacktestRequests"); + + migrationBuilder.DropIndex( + name: "IX_BundleBacktestRequests_UserId", + table: "BundleBacktestRequests"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "BundleBacktestRequests"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.Designer.cs new file mode 100644 index 0000000..4b73028 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.Designer.cs @@ -0,0 +1,1360 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity")] + partial class RemoveOptimizedMoneyManagementJsonFromBacktestEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.cs b/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.cs new file mode 100644 index 0000000..90bade7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725092603_RemoveOptimizedMoneyManagementJsonFromBacktestEntity.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class RemoveOptimizedMoneyManagementJsonFromBacktestEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "OptimizedMoneyManagementJson", + table: "Backtests"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OptimizedMoneyManagementJson", + table: "Backtests", + type: "jsonb", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.Designer.cs new file mode 100644 index 0000000..3c957ad --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.Designer.cs @@ -0,0 +1,1375 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250725172635_AddUserIdToMoneyManagement")] + partial class AddUserIdToMoneyManagement + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.cs b/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.cs new file mode 100644 index 0000000..2e7d542 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725172635_AddUserIdToMoneyManagement.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddUserIdToMoneyManagement : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "MoneyManagements", + type: "integer", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_MoneyManagements_UserId", + table: "MoneyManagements", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_MoneyManagements_Users_UserId", + table: "MoneyManagements", + column: "UserId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_MoneyManagements_Users_UserId", + table: "MoneyManagements"); + + migrationBuilder.DropIndex( + name: "IX_MoneyManagements_UserId", + table: "MoneyManagements"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "MoneyManagements"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.Designer.cs new file mode 100644 index 0000000..01d42bf --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.Designer.cs @@ -0,0 +1,1390 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250725173315_AddUserIdToBotBackup")] + partial class AddUserIdToBotBackup + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cost") + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Exchange") + .IsUnique(); + + b.HasIndex("LastUpdate"); + + b.ToTable("Fees"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.cs b/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.cs new file mode 100644 index 0000000..153d1dd --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725173315_AddUserIdToBotBackup.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddUserIdToBotBackup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "BotBackups", + type: "integer", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_BotBackups_UserId", + table: "BotBackups", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_BotBackups_Users_UserId", + table: "BotBackups", + column: "UserId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_BotBackups_Users_UserId", + table: "BotBackups"); + + migrationBuilder.DropIndex( + name: "IX_BotBackups_UserId", + table: "BotBackups"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "BotBackups"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.Designer.cs new file mode 100644 index 0000000..72b7e08 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.Designer.cs @@ -0,0 +1,1356 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250725202808_RemoveFeeEntity")] + partial class RemoveFeeEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.cs b/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.cs new file mode 100644 index 0000000..c530c5c --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250725202808_RemoveFeeEntity.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class RemoveFeeEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Fees"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Fees", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Cost = table.Column(type: "numeric(18,8)", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Exchange = table.Column(type: "text", nullable: false), + LastUpdate = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Fees", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Fees_Exchange", + table: "Fees", + column: "Exchange", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Fees_LastUpdate", + table: "Fees", + column: "LastUpdate"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs new file mode 100644 index 0000000..9e56873 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs @@ -0,0 +1,1353 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + partial class ManagingDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EndDate"); + + b.HasIndex("FinalPnl"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("StartDate"); + + b.HasIndex("UserName"); + + b.HasIndex("WinRate"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastStatus") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreateDate"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("LastStatus"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreateDate"); + + b.ToTable("BotBackups"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Direction"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("Type"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Initiator"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("ScenarioCount"); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Rank"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs b/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs deleted file mode 100644 index d4faa09..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq.Expressions; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Driver; - -namespace Managing.Infrastructure.Databases.MongoDb.Abstractions -{ - public interface IMongoRepository where TDocument : IDocument - { - IQueryable AsQueryable(); - - IEnumerable FilterBy( - Expression> filterExpression); - - IEnumerable FindAll(); - IEnumerable FilterBy(FilterDefinition filter); - - IEnumerable FilterBy( - Expression> filterExpression, - Expression> projectionExpression); - - TDocument FindOne(Expression> filterExpression); - - Task FindOneAsync(Expression> filterExpression); - - TDocument FindById(string id); - - Task FindByIdAsync(string id); - - void InsertOne(TDocument document); - - Task InsertOneAsync(TDocument document); - - void InsertMany(ICollection documents); - - Task InsertManyAsync(ICollection documents); - - void ReplaceOne(TDocument document); - - Task ReplaceOneAsync(TDocument document); - - void DeleteOne(Expression> filterExpression); - - Task DeleteOneAsync(Expression> filterExpression); - - void DeleteById(string id); - - Task DeleteByIdAsync(string id); - - void DeleteMany(Expression> filterExpression); - - Task DeleteManyAsync(Expression> filterExpression); - - void Update(TDocument entity); - void CreateIndex(string column); - void DropCollection(); - IMongoCollection GetCollection(); - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs b/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs deleted file mode 100644 index 801575c..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Managing.Infrastructure.Databases.MongoDb.Attributes -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class BsonCollectionAttribute : Attribute - { - public string CollectionName { get; } - - public BsonCollectionAttribute(string collectionName) - { - CollectionName = collectionName; - } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs deleted file mode 100644 index 3a99df6..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Accounts")] -public class AccountDto : Document -{ - public UserDto User { get; set; } - public string Name { get; set; } - public TradingExchanges Exchanges { get; set; } - public AccountType Type { get; set; } - public string Key { get; set; } - public string Secret { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs deleted file mode 100644 index 900822a..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Exilion.TradingAtomics; -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Backtests")] - public class BacktestDto : Document - { - [BsonRepresentation(BsonType.Decimal128)] - public decimal FinalPnl { get; set; } - - public int WinRate { get; set; } - [BsonRepresentation(BsonType.Decimal128)] - public decimal GrowthPercentage { get; set; } - [BsonRepresentation(BsonType.Decimal128)] - public decimal HodlPercentage { get; set; } - public TradingBotConfigDto Config { get; set; } - public List Positions { get; set; } - public List Signals { get; set; } - public DateTime StartDate { get; set; } - public DateTime EndDate { get; set; } - public MoneyManagementDto MoneyManagement { get; internal set; } - public MoneyManagementDto OptimizedMoneyManagement { get; internal set; } - public UserDto User { get; set; } - public PerformanceMetrics Statistics { get; set; } - [BsonRepresentation(BsonType.Decimal128)] - public decimal Fees { get; set; } - public double Score { get; set; } - public string ScoreMessage { get; set; } = string.Empty; - public string Identifier { get; set; } - public string RequestId { get; set; } - public string? Metadata { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs deleted file mode 100644 index db2284f..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - - -[BsonCollection("BadTrader")] -public class BadTraderDto : Document -{ - public string Address { get; set; } - public int Winrate { get; set; } - public decimal Pnl { get; set; } - public int TradeCount { get; set; } - public decimal AverageWin { get; set; } - public decimal AverageLoss { get; set; } - public decimal Roi { get; set; } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs deleted file mode 100644 index 52940f9..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("BestTrader")] -public class BestTraderDto : Document -{ - public string Address { get; set; } - public int Winrate { get; set; } - public decimal Pnl { get; set; } - public int TradeCount { get; set; } - public decimal AverageWin { get; set; } - public decimal AverageLoss { get; set; } - public decimal Roi { get; set; } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs deleted file mode 100644 index 76885bb..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Bots")] -public class BotDto : Document -{ - public string Data { get; set; } - public string Identifier { get; set; } - public UserDto User { get; set; } - public BotStatus LastStatus { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs deleted file mode 100644 index 85340e5..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Managing.Domain.Backtests; -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("BundleBacktestRequests")] -public class BundleBacktestRequestDto : Document -{ - public string RequestId { get; set; } = string.Empty; - public UserDto User { get; set; } = new(); - public DateTime? CompletedAt { get; set; } - public BundleBacktestRequestStatus Status { get; set; } - public string BacktestRequestsJson { get; set; } = string.Empty; - public int TotalBacktests { get; set; } - public int CompletedBacktests { get; set; } - public int FailedBacktests { get; set; } - public string? ErrorMessage { get; set; } - public string? ProgressInfo { get; set; } - public string? CurrentBacktest { get; set; } - public int? EstimatedTimeRemainingSeconds { get; set; } - public string Name { get; set; } = string.Empty; - public List Results { get; set; } = new(); -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs deleted file mode 100644 index 1341fb2..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Bson.Serialization.Attributes; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Candles")] - public class CandleDto : Document - { - public TradingExchanges Exchange { get; set; } - public Timeframe Timeframe { get; set; } - public string Ticker { get; set; } - [BsonDateTimeOptions] - public DateTime OpenTime { get; set; } - [BsonDateTimeOptions] - public DateTime CloseTime { get; set; } - public decimal Open { get; set; } - public decimal Close { get; set; } - public decimal High { get; set; } - public decimal Low { get; set; } - public decimal BaseVolume { get; set; } - public decimal QuoteVolume { get; set; } - public int TradeCount { get; set; } - public decimal TakerBuyBaseVolume { get; set; } - public decimal TakerBuyQuoteVolume { get; set; } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs deleted file mode 100644 index ea2a718..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Fees")] -public class FeeDto : Document -{ - public decimal Cost { get; set; } - public TradingExchanges Exchange { get; set; } - public DateTime LastUpdate { get; set; } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/FundingRateDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/FundingRateDto.cs deleted file mode 100644 index df5c773..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/FundingRateDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Managing.Common; -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("FundingRates")] -public class FundingRateDto : Document -{ - public Enums.Ticker Ticker { get; set; } - public decimal Rate { get; set; } - public Enums.TradingExchanges Exchange { get; set; } - public DateTime Date { get; set; } - public Enums.TradeDirection Direction { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/GeneticRequestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/GeneticRequestDto.cs deleted file mode 100644 index c9f9921..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/GeneticRequestDto.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("GeneticRequests")] - public class GeneticRequestDto : Document - { - public string RequestId { get; set; } - public UserDto User { get; set; } - public DateTime? CompletedAt { get; set; } - public string Status { get; set; } - public Ticker Ticker { get; set; } - public Timeframe Timeframe { get; set; } - public DateTime StartDate { get; set; } - public DateTime EndDate { get; set; } - public decimal Balance { get; set; } - public int PopulationSize { get; set; } - public int Generations { get; set; } - public double MutationRate { get; set; } - public GeneticSelectionMethod SelectionMethod { get; set; } - public GeneticCrossoverMethod CrossoverMethod { get; set; } - public GeneticMutationMethod MutationMethod { get; set; } - public int ElitismPercentage { get; set; } - public double MaxTakeProfit { get; set; } - public List EligibleIndicators { get; set; } = new(); - public double? BestFitness { get; set; } - public string? BestIndividual { get; set; } - public string? ErrorMessage { get; set; } - public string? ProgressInfo { get; set; } - public string? BestChromosome { get; set; } - public double? BestFitnessSoFar { get; set; } - public int CurrentGeneration { get; set; } - public DateTime? UpdatedAt { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/IndicatorDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/IndicatorDto.cs deleted file mode 100644 index 6d6b9c6..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/IndicatorDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Indicators")] - public class IndicatorDto : Document - { - public IndicatorType Type { get; set; } - public Timeframe Timeframe { get; set; } - public string Name { get; set; } - public int MinimumHistory { get; set; } - public int? Period { get; set; } - public int? FastPeriods { get; set; } - public int? SlowPeriods { get; set; } - public int? SignalPeriods { get; set; } - public double? Multiplier { get; set; } - public int? StochPeriods { get; set; } - public int? SmoothPeriods { get; set; } - public int? CyclePeriods { get; set; } - public SignalType SignalType { get; set; } - public UserDto User { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs deleted file mode 100644 index e3c7341..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("MoneyManagement")] - public class MoneyManagementDto : Document - { - public Timeframe Timeframe { get; set; } - public RiskLevel RiskLevel { get; set; } - public decimal BalanceAtRisk { get; set; } - public decimal StopLoss { get; set; } - public decimal TakeProfit { get; set; } - public decimal Leverage { get; set; } - public string Name { get; internal set; } - public UserDto User { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs deleted file mode 100644 index 6ef1dae..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Bson.Serialization.Attributes; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Positions")] - public class PositionDto : Document - { - [BsonDateTimeOptions] - public DateTime Date { get; set; } - public TradeDto Open { get; set; } - public TradeDto StopLoss { get; set; } - public TradeDto TakeProfit1 { get; set; } - public TradeDto TakeProfit2 { get; set; } - public decimal ProfitAndLoss { get; set; } - public TradeDirection OriginDirection { get; set; } - public string Identifier { get; set; } - public PositionStatus Status { get; set; } - public Ticker Ticker { get; set; } - public string SignalIdentifier { get; set; } - public string AccountName { get; set; } - public MoneyManagementDto MoneyManagement { get; set; } - public PositionInitiator Initiator { get; set; } - public UserDto User { get; set; } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/RiskManagementDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/RiskManagementDto.cs deleted file mode 100644 index 1071cc0..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/RiskManagementDto.cs +++ /dev/null @@ -1,22 +0,0 @@ -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - public class RiskManagementDto - { - public decimal AdverseProbabilityThreshold { get; set; } - public decimal FavorableProbabilityThreshold { get; set; } - public decimal RiskAversion { get; set; } - public decimal KellyMinimumThreshold { get; set; } - public decimal KellyMaximumCap { get; set; } - public decimal MaxLiquidationProbability { get; set; } - public int SignalValidationTimeHorizonHours { get; set; } - public int PositionMonitoringTimeHorizonHours { get; set; } - public decimal PositionWarningThreshold { get; set; } - public decimal PositionAutoCloseThreshold { get; set; } - public decimal KellyFractionalMultiplier { get; set; } - public RiskToleranceLevel RiskTolerance { get; set; } - public bool UseExpectedUtility { get; set; } - public bool UseKellyCriterion { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs deleted file mode 100644 index b3c488f..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Scenarios")] - public class ScenarioDto : Document - { - public string Name { get; set; } - public List Indicators { get; set; } - public int LoopbackPeriod { get; set; } - public UserDto User { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs deleted file mode 100644 index 44d9a2f..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Signals")] - public class SignalDto : Document - { - public TradeDirection Direction { get; set; } - public Confidence Confidence { get; set; } - public DateTime Date { get; set; } - public CandleDto Candle { get; set; } - public string Identifier { get; set; } - public Ticker Ticker { get; set; } - public SignalStatus Status { get; set; } - public Timeframe Timeframe { get; set; } - public IndicatorType Type { get; set; } - public SignalType SignalType { get; set; } - public UserDto User { get; set; } - public string IndicatorName { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs deleted file mode 100644 index 9d4551d..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("SpotlighOverview")] -public class SpotlighOverviewDto : Document -{ - public List Spotlights { get; set; } - public DateTime DateTime { get; set; } - public Guid Identifier { get; set; } - public int ScenarioCount { get; set; } -} - -public class SpotlightDto -{ - public ScenarioDto Scenario { get; set; } - public List TickerSignals { get; set; } -} - -public class TickerSignalDto -{ - public Ticker Ticker { get; set; } - public List FiveMinutes { get; set; } - public List FifteenMinutes { get; set; } - public List OneHour { get; set; } - public List FourHour { get; set; } - public List OneDay { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersLeaderboardDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersLeaderboardDto.cs deleted file mode 100644 index 77b449e..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersLeaderboardDto.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -/// -/// MongoDB DTO for storing Synth miners leaderboard data -/// -[BsonCollection("SynthMinersLeaderboard")] -public class SynthMinersLeaderboardDto : Document -{ - /// - /// Asset symbol (e.g., "BTC", "ETH") - /// - public string Asset { get; set; } - - /// - /// Time increment used for this leaderboard data - /// - public int TimeIncrement { get; set; } - - /// - /// Signal date for which this leaderboard was retrieved (for backtests) - /// Null for live trading data - /// - public DateTime? SignalDate { get; set; } - - /// - /// Whether this is backtest data or live data - /// - public bool IsBacktest { get; set; } - - /// - /// Serialized JSON of miners list - /// - public string MinersData { get; set; } - - /// - /// Cache key for quick lookup - /// - public string CacheKey { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersPredictionsDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersPredictionsDto.cs deleted file mode 100644 index 6fa672d..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthMinersPredictionsDto.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -/// -/// MongoDB DTO for storing Synth miners predictions data -/// -[BsonCollection("SynthMinersPredictions")] -public class SynthMinersPredictionsDto : Document -{ - /// - /// Asset symbol (e.g., "BTC", "ETH") - /// - public string Asset { get; set; } - - /// - /// Time increment used for these predictions - /// - public int TimeIncrement { get; set; } - - /// - /// Time length (horizon) for these predictions in seconds - /// - public int TimeLength { get; set; } - - /// - /// Signal date for which these predictions were retrieved (for backtests) - /// Null for live trading data - /// - public DateTime? SignalDate { get; set; } - - /// - /// Whether this is backtest data or live data - /// - public bool IsBacktest { get; set; } - - /// - /// Serialized JSON of miner UIDs list - /// - public string MinerUidsData { get; set; } - - /// - /// Serialized JSON of predictions data - /// - public string PredictionsData { get; set; } - - /// - /// When this prediction data was fetched from the API - /// - public DateTime FetchedAt { get; set; } - - /// - /// Cache key for quick lookup - /// - public string CacheKey { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthPredictionDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthPredictionDto.cs deleted file mode 100644 index 3cea1fa..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/SynthPredictionDto.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -/// -/// MongoDB DTO for storing individual Synth miner prediction data -/// -[BsonCollection("SynthPredictions")] -public class SynthPredictionDto : Document -{ - /// - /// Asset symbol (e.g., "BTC", "ETH") - /// - public string Asset { get; set; } - - /// - /// Miner UID that provided this prediction - /// - public int MinerUid { get; set; } - - /// - /// Time increment used for this prediction - /// - public int TimeIncrement { get; set; } - - /// - /// Time length (horizon) for this prediction in seconds - /// - public int TimeLength { get; set; } - - /// - /// Signal date for which this prediction was retrieved (for backtests) - /// Null for live trading data - /// - public DateTime? SignalDate { get; set; } - - /// - /// Whether this is backtest data or live data - /// - public bool IsBacktest { get; set; } - - /// - /// Serialized JSON of the prediction data - /// - public string PredictionData { get; set; } - - /// - /// Cache key for quick lookup - /// - public string CacheKey { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs deleted file mode 100644 index a805032..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("TopVolumeTickers")] -public class TopVolumeTickerDto : Document -{ - public Ticker Ticker { get; set; } - public DateTime Date { get; set; } - public decimal Volume { get; set; } - public int Rank { get; set; } - public TradingExchanges Exchange { get; set; } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs deleted file mode 100644 index 6829121..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Bson.Serialization.Attributes; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - [BsonCollection("Trades")] - public class TradeDto : Document - { - [BsonDateTimeOptions] - public DateTime Date { get; set; } - public TradeDirection Direction { get; set; } - public TradeStatus Status { get; set; } - public TradeType TradeType { get; set; } - public Ticker Ticker { get; set; } - public decimal Fee { get; set; } - public decimal Quantity { get; set; } - public decimal Price { get; set; } - public decimal Leverage { get; set; } - public string ExchangeOrderId { get; set; } - public string Message { get; set; } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/TradingBotConfigDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/TradingBotConfigDto.cs deleted file mode 100644 index 1ff1118..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/TradingBotConfigDto.cs +++ /dev/null @@ -1,29 +0,0 @@ -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections -{ - public class TradingBotConfigDto - { - public string AccountName { get; set; } - public MoneyManagementDto MoneyManagement { get; set; } - public Ticker Ticker { get; set; } - public Timeframe Timeframe { get; set; } - public bool IsForWatchingOnly { get; set; } - public decimal BotTradingBalance { get; set; } - public bool IsForBacktest { get; set; } - public int CooldownPeriod { get; set; } - public int MaxLossStreak { get; set; } - public bool FlipPosition { get; set; } - public string Name { get; set; } - public RiskManagementDto RiskManagement { get; set; } - public ScenarioDto Scenario { get; set; } - public string ScenarioName { get; set; } - public decimal? MaxPositionTimeHours { get; set; } - public bool CloseEarlyWhenProfitable { get; set; } - public bool FlipOnlyWhenInProfit { get; set; } - public bool UseSynthApi { get; set; } - public bool UseForPositionSizing { get; set; } - public bool UseForSignalFiltering { get; set; } - public bool UseForDynamicStopLoss { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs deleted file mode 100644 index cc35b7d..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Users")] -public class UserDto : Document -{ - public string Name { get; set; } - public string AgentName { get; set; } - public string AvatarUrl { get; set; } - public string TelegramChannel { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs deleted file mode 100644 index a9e1de2..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Workers")] -public class WorkerDto : Document -{ - public WorkerType WorkerType { get; set; } - public DateTime StartTime { get; set; } - public DateTime? LastRunTime { get; set; } - public int ExecutionCount { get; set; } - public TimeSpan Delay { get; set; } - public bool IsActive { get; set; } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs deleted file mode 100644 index dcc363f..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Managing.Domain.Workflows.Synthetics; -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb.Collections; - -[BsonCollection("Workflow")] -public class WorkflowDto : Document -{ - public string Name { get; set; } - public string Description { get; set; } - public WorkflowUsage Usage { get; set; } - public List Flows { get; set; } -} - diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs deleted file mode 100644 index 6d3cef5..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Managing.Infrastructure.Databases.MongoDb.Configurations -{ - public abstract class Document : IDocument - { - [BsonId] - public ObjectId Id { get; set; } - [BsonDateTimeOptions] - public DateTime CreatedAt => Id.CreationTime; - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs deleted file mode 100644 index 46663df..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Managing.Infrastructure.Databases.MongoDb.Configurations -{ - public interface IDocument - { - [BsonId] - [BsonRepresentation(BsonType.String)] - ObjectId Id { get; set; } - - DateTime CreatedAt { get; } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs deleted file mode 100644 index 74c0227..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Managing.Infrastructure.Databases.MongoDb.Configurations -{ - public interface IManagingDatabaseSettings - { - string ConnectionString { get; set; } - string DatabaseName { get; set; } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs deleted file mode 100644 index 4029a99..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Managing.Infrastructure.Databases.MongoDb.Configurations; - -public class ManagingDatabaseSettings : IManagingDatabaseSettings -{ - public string ConnectionString { get; set; } - public string DatabaseName { get; set; } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/IndexService.cs b/src/Managing.Infrastructure.Database/MongoDb/IndexService.cs deleted file mode 100644 index 3e50663..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/IndexService.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Managing.Infrastructure.Databases.MongoDb.Collections; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using MongoDB.Driver; - -namespace Managing.Infrastructure.Databases.MongoDb; - -/// -/// Service responsible for creating and managing MongoDB indexes -/// -public class IndexService -{ - private readonly IMongoDatabase _database; - private readonly ILogger _logger; - - public IndexService( - IOptions databaseSettings, - ILogger logger) - { - var settings = databaseSettings.Value; - var client = new MongoClient(settings.ConnectionString); - _database = client.GetDatabase(settings.DatabaseName); - _logger = logger; - } - - /// - /// Creates all necessary indexes for the application - /// - public async Task CreateIndexesAsync() - { - try - { - _logger.LogInformation("Creating MongoDB indexes..."); - - // Create indexes for BacktestDto - await CreateBacktestIndexesAsync(); - - await CreateBundleBacktestIndexesAsync(); - - // Create indexes for GeneticRequestDto - await CreateGeneticRequestIndexesAsync(); - - _logger.LogInformation("MongoDB indexes created successfully"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create MongoDB indexes"); - throw; - } - } - - /// - /// Creates indexes for the BacktestDto collection - /// - private async Task CreateBacktestIndexesAsync() - { - try - { - var collection = _database.GetCollection("Backtests"); - - // Create index on RequestId for faster queries - var requestIdIndexKeys = Builders.IndexKeys.Ascending(b => b.RequestId); - var requestIdIndexOptions = new CreateIndexOptions { Name = "RequestId_Index" }; - var requestIdIndexModel = new CreateIndexModel(requestIdIndexKeys, requestIdIndexOptions); - - // Create index (MongoDB will ignore if it already exists) - await collection.Indexes.CreateOneAsync(requestIdIndexModel); - - _logger.LogInformation("Backtest RequestId index created successfully"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create Backtest indexes"); - throw; - } - } - - private async Task CreateBundleBacktestIndexesAsync() - { - var bundleCollection = _database.GetCollection("BundleBacktestRequests"); - // Index on RequestId (unique) - var requestIdIndex = Builders.IndexKeys.Ascending(b => b.RequestId); - await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel(requestIdIndex, new CreateIndexOptions { Unique = true })); - // Index on User.Name (non-unique) - var userNameIndex = Builders.IndexKeys.Ascending("User.Name"); - await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel(userNameIndex)); - } - - /// - /// Creates indexes for the GeneticRequestDto collection - /// - private async Task CreateGeneticRequestIndexesAsync() - { - try - { - var collection = _database.GetCollection("GeneticRequests"); - - // Create index on RequestId for faster queries - var requestIdIndexKeys = Builders.IndexKeys.Ascending(gr => gr.RequestId); - var requestIdIndexOptions = new CreateIndexOptions { Name = "RequestId_Index" }; - var requestIdIndexModel = new CreateIndexModel(requestIdIndexKeys, requestIdIndexOptions); - - // Create index on User.Name for user-specific queries - var userIndexKeys = Builders.IndexKeys.Ascending("User.Name"); - var userIndexOptions = new CreateIndexOptions { Name = "User_Name_Index" }; - var userIndexModel = new CreateIndexModel(userIndexKeys, userIndexOptions); - - // Create index on Status for filtering by status - var statusIndexKeys = Builders.IndexKeys.Ascending(gr => gr.Status); - var statusIndexOptions = new CreateIndexOptions { Name = "Status_Index" }; - var statusIndexModel = new CreateIndexModel(statusIndexKeys, statusIndexOptions); - - // Create indexes (MongoDB will ignore if they already exist) - await collection.Indexes.CreateManyAsync(new[] - { - requestIdIndexModel, - userIndexModel, - statusIndexModel - }); - - _logger.LogInformation("GeneticRequest indexes created successfully"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create GeneticRequest indexes"); - throw; - } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs deleted file mode 100644 index b0cb9d5..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MongoDB.Bson; -using MongoDB.Driver; - -namespace Managing.Infrastructure.Databases.MongoDb -{ - public static class MongoHelpers - { - public static async Task EnsureIndexExists(this IMongoDatabase database, string collectionName, string indexName) - { - var collection = database.GetCollection(collectionName); - var index = new BsonDocument - { - {indexName, 1} - }; - - var indexModel = new CreateIndexModel(index, new CreateIndexOptions { Unique = true }); - await collection.Indexes.CreateOneAsync(indexModel).ConfigureAwait(false); - } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs deleted file mode 100644 index 7d2577f..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ /dev/null @@ -1,1160 +0,0 @@ -using System.Text.Json; -using Managing.Domain.Accounts; -using Managing.Domain.Backtests; -using Managing.Domain.Bots; -using Managing.Domain.Candles; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Risk; -using Managing.Domain.Scenarios; -using Managing.Domain.Statistics; -using Managing.Domain.Strategies; -using Managing.Domain.Synth.Models; -using Managing.Domain.Trades; -using Managing.Domain.Users; -using Managing.Domain.Workers; -using Managing.Domain.Workflows.Synthetics; -using Managing.Infrastructure.Databases.MongoDb.Collections; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases.MongoDb; - -public static class MongoMappers -{ - #region Statistics - - internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker) - { - return new TopVolumeTickerDto - { - Ticker = topVolumeTicker.Ticker, - Date = topVolumeTicker.Date, - Volume = topVolumeTicker.Volume, - Rank = topVolumeTicker.Rank, - Exchange = topVolumeTicker.Exchange - }; - } - - internal static IList Map(IEnumerable top) - { - return top.Select(topElement => new TopVolumeTicker - { - Ticker = topElement.Ticker, - Date = topElement.Date, - Volume = topElement.Volume, - Rank = topElement.Rank, - Exchange = topElement.Exchange - }).ToList(); - } - - #endregion - - #region Accounts - - internal static AccountDto Map(Account request) - { - return new AccountDto - { - Name = request.Name, - Exchanges = request.Exchange, - Key = request.Key, - Secret = request.Secret, - Type = request.Type, - User = Map(request.User) - }; - } - - internal static IEnumerable Map(IEnumerable accounts) - { - return accounts.Select(account => Map(account)); - } - - internal static Account Map(AccountDto account, bool hideKeys = false) - { - if (account == null) return null; - - var a = new Account - { - Name = account.Name, - Exchange = account.Exchanges, - }; - - if (!hideKeys) - { - a.Key = account.Key; - a.Secret = account.Secret; - } - - a.Exchange = account.Exchanges; - a.Type = account.Type; - a.User = Map(account.User); - - return a; - } - - #endregion - - #region Workers - - internal static WorkerDto Map(Worker worker) - { - return new WorkerDto - { - WorkerType = worker.WorkerType, - StartTime = worker.StartTime, - LastRunTime = worker.LastRunTime, - ExecutionCount = worker.ExecutionCount, - Delay = worker.Delay - }; - } - - internal static Worker Map(WorkerDto worker) - { - if (worker == null) - return null; - - return new Worker - { - WorkerType = worker.WorkerType, - StartTime = worker.StartTime, - LastRunTime = worker.LastRunTime, - ExecutionCount = worker.ExecutionCount, - Delay = worker.Delay, - IsActive = worker.IsActive - }; - } - - #endregion - - #region Backtests - - internal static Backtest Map(BacktestDto b) - { - if (b == null) - return null; - - var config = Map(b.Config); - - var bTest = new Backtest( - config, - b.Positions?.Select(p => Map(p)).ToList() ?? new List(), - b.Signals?.Select(s => Map(s)).ToList() ?? new List()) - { - FinalPnl = b.FinalPnl, - WinRate = b.WinRate, - GrowthPercentage = b.GrowthPercentage, - HodlPercentage = b.HodlPercentage, - Id = b.Identifier, - OptimizedMoneyManagement = Map(b.OptimizedMoneyManagement), - User = Map(b.User), - Statistics = b.Statistics, - StartDate = b.StartDate, - EndDate = b.EndDate, - Score = b.Score, - ScoreMessage = b.ScoreMessage ?? string.Empty, - RequestId = b.RequestId, - Metadata = string.IsNullOrEmpty(b.Metadata) ? null : JsonSerializer.Deserialize(b.Metadata) - }; - - return bTest; - } - - internal static BacktestDto Map(Backtest result) - { - if (result == null) - return null; - - return new BacktestDto - { - Identifier = result.Id, - FinalPnl = result.FinalPnl, - WinRate = result.WinRate, - GrowthPercentage = result.GrowthPercentage, - HodlPercentage = result.HodlPercentage, - Config = Map(result.Config), - Positions = Map(result.Positions), - Signals = result.Signals.Select(s => Map(s)).ToList(), - MoneyManagement = Map(result.Config.MoneyManagement), - OptimizedMoneyManagement = Map(result.OptimizedMoneyManagement), - User = Map(result.User), - Statistics = result.Statistics, - StartDate = result.StartDate, - EndDate = result.EndDate, - Score = result.Score, - ScoreMessage = result.ScoreMessage ?? string.Empty, - RequestId = result.RequestId, - Metadata = result.Metadata == null ? null : JsonSerializer.Serialize(result.Metadata) - }; - } - - #endregion - - #region Genetic Requests - - internal static GeneticRequest Map(GeneticRequestDto dto) - { - if (dto == null) - return null; - - return new GeneticRequest - { - RequestId = dto.RequestId, - User = Map(dto.User), - CreatedAt = dto.CreatedAt, - CompletedAt = dto.CompletedAt, - Status = Enum.Parse(dto.Status), - Ticker = dto.Ticker, - Timeframe = dto.Timeframe, - StartDate = dto.StartDate, - EndDate = dto.EndDate, - Balance = dto.Balance, - PopulationSize = dto.PopulationSize, - Generations = dto.Generations, - MutationRate = dto.MutationRate, - SelectionMethod = dto.SelectionMethod, - CrossoverMethod = dto.CrossoverMethod, - MutationMethod = dto.MutationMethod, - ElitismPercentage = dto.ElitismPercentage, - MaxTakeProfit = dto.MaxTakeProfit, - EligibleIndicators = dto.EligibleIndicators, - BestFitness = dto.BestFitness, - BestIndividual = dto.BestIndividual, - ErrorMessage = dto.ErrorMessage, - ProgressInfo = dto.ProgressInfo, - BestChromosome = dto.BestChromosome, - BestFitnessSoFar = dto.BestFitnessSoFar, - CurrentGeneration = dto.CurrentGeneration - }; - } - - internal static GeneticRequestDto Map(GeneticRequest geneticRequest) - { - if (geneticRequest == null) - return null; - - return new GeneticRequestDto - { - RequestId = geneticRequest.RequestId, - User = Map(geneticRequest.User), - CompletedAt = geneticRequest.CompletedAt, - Status = geneticRequest.Status.ToString(), - Ticker = geneticRequest.Ticker, - Timeframe = geneticRequest.Timeframe, - StartDate = geneticRequest.StartDate, - EndDate = geneticRequest.EndDate, - Balance = geneticRequest.Balance, - PopulationSize = geneticRequest.PopulationSize, - Generations = geneticRequest.Generations, - MutationRate = geneticRequest.MutationRate, - SelectionMethod = geneticRequest.SelectionMethod, - CrossoverMethod = geneticRequest.CrossoverMethod, - MutationMethod = geneticRequest.MutationMethod, - ElitismPercentage = geneticRequest.ElitismPercentage, - MaxTakeProfit = geneticRequest.MaxTakeProfit, - EligibleIndicators = geneticRequest.EligibleIndicators, - BestFitness = geneticRequest.BestFitness, - BestIndividual = geneticRequest.BestIndividual, - ErrorMessage = geneticRequest.ErrorMessage, - ProgressInfo = geneticRequest.ProgressInfo, - BestChromosome = geneticRequest.BestChromosome, - BestFitnessSoFar = geneticRequest.BestFitnessSoFar, - CurrentGeneration = geneticRequest.CurrentGeneration - }; - } - - #endregion - - #region Candles - - public static Candle Map(CandleDto candle) - { - if (candle == null) - return null; - - return new Candle() - { - Ticker = candle.Ticker, - BaseVolume = candle.BaseVolume, - Close = candle.Close, - Date = candle.OpenTime, - Open = candle.Open, - OpenTime = candle.OpenTime, - High = candle.High, - Low = candle.Low, - QuoteVolume = candle.QuoteVolume, - TakerBuyBaseVolume = candle.TakerBuyBaseVolume, - TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, - TradeCount = candle.TradeCount, - Exchange = candle.Exchange, - }; - } - - public static CandleDto Map(Candle candle) - { - return new CandleDto - { - Exchange = candle.Exchange, - Ticker = candle.Ticker, - OpenTime = candle.OpenTime, - Open = candle.Open, - Close = candle.Close, - High = candle.High, - Low = candle.Low, - BaseVolume = candle.BaseVolume, - QuoteVolume = candle.QuoteVolume, - TradeCount = candle.TradeCount, - TakerBuyBaseVolume = candle.TakerBuyBaseVolume, - TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume - }; - } - - public static List Map(List candles) - { - return candles.ConvertAll(candle => Map(candle)); - } - - #endregion - - #region Positions - - public static PositionDto Map(Position position) - { - var p = new PositionDto - { - Date = position.Date, - Open = Map(position.Open), - OriginDirection = position.OriginDirection, - Identifier = position.Identifier, - SignalIdentifier = position.SignalIdentifier, - Status = position.Status, - AccountName = position.AccountName, - MoneyManagement = Map(position.MoneyManagement), - Initiator = position.Initiator, - Ticker = position.Ticker, - User = Map(position.User) - }; - - if (position.StopLoss != null) - p.StopLoss = Map(position.StopLoss); - - if (position.TakeProfit1 != null) - p.TakeProfit1 = Map(position.TakeProfit1); - - if (position.TakeProfit2 != null) - p.TakeProfit2 = Map(position.TakeProfit2); - - if (position.ProfitAndLoss != null) - p.ProfitAndLoss = position.ProfitAndLoss.Realized; - - return p; - } - - private static TradeDto Map(Trade trade) - { - return new TradeDto - { - Date = trade.Date, - Direction = trade.Direction, - Status = trade.Status, - TradeType = trade.TradeType, - Ticker = trade.Ticker, - Quantity = trade.Quantity, - Price = trade.Price, - Leverage = trade.Leverage, - ExchangeOrderId = trade.ExchangeOrderId, - Message = trade.Message - }; - } - - public static Position Map(PositionDto dto) - { - var position = new Position(dto.Identifier, dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker, - Map(dto.MoneyManagement), dto.Initiator, dto.Date, Map(dto.User)) - { - Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status, - tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity, - price: dto.Open.Price, leverage: dto.Open.Leverage, exchangeOrderId: dto.Open.ExchangeOrderId, - message: dto.Open.Message), - ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss }, - Status = dto.Status, - SignalIdentifier = dto.SignalIdentifier, - Identifier = dto.Identifier, - User = Map(dto.User) - }; - - if (dto.StopLoss != null) - { - position.StopLoss = new Trade(date: dto.StopLoss.Date, direction: dto.StopLoss.Direction, - status: dto.StopLoss.Status, tradeType: dto.StopLoss.TradeType, ticker: dto.StopLoss.Ticker, - quantity: dto.StopLoss.Quantity, price: dto.StopLoss.Price, leverage: dto.StopLoss.Leverage, - exchangeOrderId: dto.StopLoss.ExchangeOrderId, message: dto.StopLoss.Message); - } - - if (dto.TakeProfit1 != null) - { - position.TakeProfit1 = new Trade(date: dto.TakeProfit1.Date, direction: dto.TakeProfit1.Direction, - status: dto.TakeProfit1.Status, tradeType: dto.TakeProfit1.TradeType, ticker: dto.TakeProfit1.Ticker, - quantity: dto.TakeProfit1.Quantity, price: dto.TakeProfit1.Price, leverage: dto.TakeProfit1.Leverage, - exchangeOrderId: dto.TakeProfit1.ExchangeOrderId, message: dto.TakeProfit1.Message); - } - - if (dto.TakeProfit2 != null) - { - position.TakeProfit2 = new Trade(date: dto.TakeProfit2.Date, direction: dto.TakeProfit2.Direction, - status: dto.TakeProfit2.Status, tradeType: dto.TakeProfit2.TradeType, ticker: dto.TakeProfit2.Ticker, - quantity: dto.TakeProfit2.Quantity, price: dto.TakeProfit2.Price, leverage: dto.TakeProfit2.Leverage, - exchangeOrderId: dto.TakeProfit2.ExchangeOrderId, message: dto.TakeProfit2.Message); - } - - return position; - } - - internal static List Map(List positions) - { - return positions.ConvertAll(position => Map(position)); - } - - #endregion - - #region Signals - - public static SignalDto Map(Signal signal) - { - return new SignalDto - { - Direction = signal.Direction, - Confidence = signal.Confidence, - Date = signal.Date, - Candle = Map(signal.Candle), - Identifier = signal.Identifier, - Ticker = signal.Ticker, - Status = signal.Status, - Timeframe = signal.Timeframe, - Type = signal.IndicatorType, - User = signal.User != null ? Map(signal.User) : null, - IndicatorName = signal.IndicatorName - }; - } - - internal static Signal Map(SignalDto bSignal) - { - var candle = Map(bSignal.Candle); - var signal = new Signal( - bSignal.Ticker, - bSignal.Direction, - bSignal.Confidence, - candle, - bSignal.Date, - TradingExchanges.Binance, //TODO FIXME When the signal status is modified from controller - bSignal.Type, - bSignal.SignalType, - bSignal.IndicatorName, - bSignal.User != null ? Map(bSignal.User) : null) - { - Status = bSignal.Status - }; - - if (bSignal.User != null) - { - signal.User = Map(bSignal.User); - } - - return signal; - } - - #endregion - - #region Scenarios - - public static ScenarioDto Map(Scenario scenario) - { - if (scenario == null) - return null; - - return new ScenarioDto - { - Name = scenario.Name, - Indicators = Map(scenario.Indicators), - LoopbackPeriod = scenario.LoopbackPeriod ?? 1, - User = scenario.User != null ? Map(scenario.User) : null - }; - } - - internal static IEnumerable Map(IEnumerable dtos) - { - return dtos.Select(d => Map(d)); - } - - internal static Scenario Map(ScenarioDto d) - { - if (d == null) - return null; - - var scenario = new Scenario(d.Name, d.LoopbackPeriod) - { - Indicators = d.Indicators.Select(s => Map(s)).ToList(), - User = d.User != null ? Map(d.User) : null - }; - return scenario; - } - - private static List Map(List indicators) - { - return indicators.ConvertAll(strategy => Map(strategy)); - } - - internal static Indicator Map(IndicatorDto indicatorDto) - { - if (indicatorDto == null) - return null; - - return new Indicator(indicatorDto.Name, indicatorDto.Type) - { - SignalType = indicatorDto.SignalType, - MinimumHistory = indicatorDto.MinimumHistory, - Period = indicatorDto.Period, - FastPeriods = indicatorDto.FastPeriods, - SlowPeriods = indicatorDto.SlowPeriods, - SignalPeriods = indicatorDto.SignalPeriods, - Multiplier = indicatorDto.Multiplier, - SmoothPeriods = indicatorDto.SmoothPeriods, - StochPeriods = indicatorDto.StochPeriods, - CyclePeriods = indicatorDto.CyclePeriods, - User = indicatorDto.User != null ? Map(indicatorDto.User) : null - }; - } - - internal static IndicatorDto Map(Indicator indicator) - { - if (indicator == null) - return null; - - return new IndicatorDto - { - Name = indicator.Name, - Type = indicator.Type, - SignalType = indicator.SignalType, - MinimumHistory = indicator.MinimumHistory, - Period = indicator.Period, - FastPeriods = indicator.FastPeriods, - SlowPeriods = indicator.SlowPeriods, - SignalPeriods = indicator.SignalPeriods, - Multiplier = indicator.Multiplier, - SmoothPeriods = indicator.SmoothPeriods, - StochPeriods = indicator.StochPeriods, - CyclePeriods = indicator.CyclePeriods, - User = indicator.User != null ? Map(indicator.User) : null - }; - } - - internal static IEnumerable Map(IEnumerable indicators) - { - return indicators.Select(indicator => Map(indicator)); - } - - #endregion - - #region Money Management - - public static MoneyManagementDto Map(MoneyManagement request) - { - if (request == null) return null; - return new MoneyManagementDto - { - Timeframe = request.Timeframe, - StopLoss = request.StopLoss, - TakeProfit = request.TakeProfit, - Leverage = request.Leverage, - Name = request.Name, - User = request.User != null ? Map(request.User) : null - }; - } - - public static MoneyManagement Map(MoneyManagementDto request) - { - if (request == null) - return null; - - return new MoneyManagement - { - Timeframe = request.Timeframe, - StopLoss = request.StopLoss, - TakeProfit = request.TakeProfit, - Leverage = request.Leverage, - Name = request.Name, - User = request.User != null ? Map(request.User) : null - }; - } - - internal static User Map(UserDto user) - { - if (user == null) - return null; - - return new User - { - Name = user.Name, - AgentName = user.AgentName, - AvatarUrl = user.AvatarUrl, - TelegramChannel = user.TelegramChannel - }; - } - - internal static UserDto Map(User user) - { - return new UserDto - { - Name = user.Name, - AgentName = user.AgentName, - AvatarUrl = user.AvatarUrl, - TelegramChannel = user.TelegramChannel - }; - } - - internal static SpotlighOverviewDto Map(SpotlightOverview overview) - { - return new SpotlighOverviewDto - { - Spotlights = Map(overview.Spotlights), - DateTime = overview.DateTime, - Identifier = overview.Identifier, - ScenarioCount = overview.ScenarioCount, - }; - } - - private static List Map(List spotlights) - { - return spotlights.ConvertAll(spotlight => new SpotlightDto - { - Scenario = new ScenarioDto - { - Name = spotlight.Scenario.Name, - Indicators = - spotlight.Scenario.Indicators.ConvertAll( - spotlightScenarioStrategy => Map(spotlightScenarioStrategy)) - }, - TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignalDto - { - Ticker = spotlightTickerSignal.Ticker, - FiveMinutes = - spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute => - Map(spotlightTickerSignalFiveMinute)) ?? new List(), - FifteenMinutes = - spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute => - Map(spotlightTickerSignalFifteenMinute)) ?? new List(), - OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour => - Map(spotlightTickerSignalOneHour)) ?? new List(), - FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour => - Map(spotlightTickerSignalFourHour)) ?? new List(), - OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay => - Map(spotlightTickerSignalOneDay)) ?? new List() - }) - }); - } - - internal static SpotlightOverview Map(SpotlighOverviewDto overview) - { - return new SpotlightOverview - { - Spotlights = Map(overview.Spotlights), - DateTime = overview.DateTime, - Identifier = overview.Identifier, - ScenarioCount = overview.ScenarioCount - }; - } - - private static List Map(List spotlights) - { - return spotlights.ConvertAll(spotlight => new Spotlight - { - Scenario = new Scenario(name: spotlight.Scenario.Name) - { - Indicators = - spotlight.Scenario.Indicators.ConvertAll( - spotlightScenarioStrategy => Map(spotlightScenarioStrategy)) - }, - TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignal - { - Ticker = spotlightTickerSignal.Ticker, - FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute => - Map(spotlightTickerSignalFiveMinute)), - FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute => - Map(spotlightTickerSignalFifteenMinute)), - OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour => - Map(spotlightTickerSignalOneHour)), - FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour => - Map(spotlightTickerSignalFourHour)), - OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay => - Map(spotlightTickerSignalOneDay)) - }) - }); - } - - internal static IList Map(IEnumerable overviews) - { - return overviews.Select(Map).ToList(); - } - - - internal static FeeDto Map(Fee fee) - { - return new FeeDto - { - Cost = fee.Cost, - Exchange = fee.Exchange, - LastUpdate = fee.LastUpdate - }; - } - - internal static Fee Map(FeeDto fee) - { - if (fee == null) return null; - - return new Fee - { - Cost = fee.Cost, - Exchange = fee.Exchange, - LastUpdate = fee.LastUpdate - }; - } - - internal static List Map(IEnumerable enumerable) - { - return enumerable.Select(enumerableElement => new Trader - { - Address = enumerableElement.Address, - Winrate = enumerableElement.Winrate, - Pnl = enumerableElement.Pnl, - TradeCount = enumerableElement.TradeCount, - AverageWin = enumerableElement.AverageWin, - AverageLoss = enumerableElement.AverageLoss, - Roi = enumerableElement.Roi - }).ToList(); - } - - internal static BestTraderDto Map(Trader trader) - { - return new BestTraderDto - { - Address = trader.Address, - Winrate = trader.Winrate, - Pnl = trader.Pnl, - TradeCount = trader.TradeCount, - AverageWin = trader.AverageWin, - AverageLoss = trader.AverageLoss, - Roi = trader.Roi - }; - } - - internal static List Map(IEnumerable enumerable) - { - return enumerable.Select(enumerableElement => new Trader - { - Address = enumerableElement.Address, - Winrate = enumerableElement.Winrate, - Pnl = enumerableElement.Pnl, - TradeCount = enumerableElement.TradeCount, - AverageWin = enumerableElement.AverageWin, - AverageLoss = enumerableElement.AverageLoss, - Roi = enumerableElement.Roi - }).ToList(); - } - - internal static BadTraderDto BadTraderMap(Trader trader) - { - return new BadTraderDto - { - Address = trader.Address, - Winrate = trader.Winrate, - Pnl = trader.Pnl, - TradeCount = trader.TradeCount, - AverageWin = trader.AverageWin, - AverageLoss = trader.AverageLoss, - Roi = trader.Roi - }; - } - - internal static WorkflowDto Map(SyntheticWorkflow workflow) - { - return new WorkflowDto - { - Name = workflow.Name, - Description = workflow.Description, - Usage = workflow.Usage, - Flows = workflow.Flows - }; - } - - internal static SyntheticWorkflow Map(WorkflowDto m) - { - if (m == null) return null; - - return new SyntheticWorkflow - { - Name = m.Name, - Usage = m.Usage, - Description = m.Description, - Flows = m.Flows.ToList(), - }; - } - - internal static BotDto Map(BotBackup bot) - { - if (bot == null) return null; - - return new BotDto - { - User = Map(bot.User), - Identifier = bot.Identifier, - Data = bot.Data, - LastStatus = bot.LastStatus - }; - } - - internal static BotBackup Map(BotDto b) - { - if (b == null) return null; - - return new BotBackup - { - User = Map(b.User), - Identifier = b.Identifier, - Data = b.Data, - LastStatus = b.LastStatus - }; - } - - #endregion - - public static FundingRate Map(FundingRateDto fundingRate) - { - if (fundingRate == null) - return null; - - return new FundingRate - { - Exchange = fundingRate.Exchange, - Rate = fundingRate.Rate, - Ticker = fundingRate.Ticker, - Date = fundingRate.Date, - Direction = fundingRate.Direction - }; - } - - public static FundingRateDto Map(FundingRate fundingRate) - { - return new FundingRateDto - { - Exchange = fundingRate.Exchange, - Rate = fundingRate.Rate, - Ticker = fundingRate.Ticker, - Date = fundingRate.Date, - Direction = fundingRate.Direction - }; - } - - #region Synth - - /// - /// Maps domain SynthMinersLeaderboard to MongoDB DTO - /// - internal static SynthMinersLeaderboardDto Map(SynthMinersLeaderboard leaderboard) - { - if (leaderboard == null) return null; - - return new SynthMinersLeaderboardDto - { - Asset = leaderboard.Asset, - TimeIncrement = leaderboard.TimeIncrement, - SignalDate = leaderboard.SignalDate, - IsBacktest = leaderboard.IsBacktest, - MinersData = JsonSerializer.Serialize(leaderboard.Miners), - CacheKey = leaderboard.GetCacheKey() - }; - } - - /// - /// Maps MongoDB DTO to domain SynthMinersLeaderboard - /// - internal static SynthMinersLeaderboard Map(SynthMinersLeaderboardDto dto) - { - if (dto == null) return null; - - var miners = string.IsNullOrEmpty(dto.MinersData) - ? new List() - : JsonSerializer.Deserialize>(dto.MinersData) ?? new List(); - - return new SynthMinersLeaderboard - { - Id = dto.Id.ToString(), - Asset = dto.Asset, - TimeIncrement = dto.TimeIncrement, - SignalDate = dto.SignalDate, - IsBacktest = dto.IsBacktest, - Miners = miners, - CreatedAt = dto.CreatedAt - }; - } - - /// - /// Maps domain SynthMinersPredictions to MongoDB DTO - /// - internal static SynthMinersPredictionsDto Map(SynthMinersPredictions predictions) - { - if (predictions == null) return null; - - return new SynthMinersPredictionsDto - { - Asset = predictions.Asset, - TimeIncrement = predictions.TimeIncrement, - TimeLength = predictions.TimeLength, - SignalDate = predictions.SignalDate, - IsBacktest = predictions.IsBacktest, - MinerUidsData = JsonSerializer.Serialize(predictions.MinerUids), - PredictionsData = JsonSerializer.Serialize(predictions.Predictions), - CacheKey = predictions.GetCacheKey() - }; - } - - /// - /// Maps MongoDB DTO to domain SynthMinersPredictions - /// - internal static SynthMinersPredictions Map(SynthMinersPredictionsDto dto) - { - if (dto == null) return null; - - var minerUids = string.IsNullOrEmpty(dto.MinerUidsData) - ? new List() - : JsonSerializer.Deserialize>(dto.MinerUidsData) ?? new List(); - - var predictions = string.IsNullOrEmpty(dto.PredictionsData) - ? new List() - : JsonSerializer.Deserialize>(dto.PredictionsData) ?? new List(); - - return new SynthMinersPredictions - { - Id = dto.Id.ToString(), - Asset = dto.Asset, - TimeIncrement = dto.TimeIncrement, - TimeLength = dto.TimeLength, - SignalDate = dto.SignalDate, - IsBacktest = dto.IsBacktest, - MinerUids = minerUids, - Predictions = predictions, - CreatedAt = dto.CreatedAt - }; - } - - /// - /// Maps domain SynthPrediction to MongoDB DTO - /// - internal static SynthPredictionDto Map(SynthPrediction prediction) - { - if (prediction == null) return null; - - return new SynthPredictionDto - { - Asset = prediction.Asset, - MinerUid = prediction.MinerUid, - TimeIncrement = prediction.TimeIncrement, - TimeLength = prediction.TimeLength, - SignalDate = prediction.SignalDate, - IsBacktest = prediction.IsBacktest, - PredictionData = JsonSerializer.Serialize(prediction.Prediction), - CacheKey = prediction.GetCacheKey() - }; - } - - /// - /// Maps MongoDB DTO to domain SynthPrediction - /// - internal static SynthPrediction Map(SynthPredictionDto dto) - { - if (dto == null) return null; - - var prediction = string.IsNullOrEmpty(dto.PredictionData) - ? null - : JsonSerializer.Deserialize(dto.PredictionData); - - return new SynthPrediction - { - Id = dto.Id.ToString(), - Asset = dto.Asset, - MinerUid = dto.MinerUid, - TimeIncrement = dto.TimeIncrement, - TimeLength = dto.TimeLength, - SignalDate = dto.SignalDate, - IsBacktest = dto.IsBacktest, - Prediction = prediction, - CreatedAt = dto.CreatedAt - }; - } - - #endregion - - #region TradingBotConfig - - public static TradingBotConfigDto Map(TradingBotConfig config) - { - if (config == null) - return null; - - return new TradingBotConfigDto - { - AccountName = config.AccountName, - MoneyManagement = Map(config.MoneyManagement), - Ticker = config.Ticker, - Timeframe = config.Timeframe, - IsForWatchingOnly = config.IsForWatchingOnly, - BotTradingBalance = config.BotTradingBalance, - IsForBacktest = config.IsForBacktest, - CooldownPeriod = config.CooldownPeriod, - MaxLossStreak = config.MaxLossStreak, - FlipPosition = config.FlipPosition, - Name = config.Name, - RiskManagement = Map(config.RiskManagement), - Scenario = Map(config.Scenario), - ScenarioName = config.ScenarioName, - MaxPositionTimeHours = config.MaxPositionTimeHours, - CloseEarlyWhenProfitable = config.CloseEarlyWhenProfitable, - FlipOnlyWhenInProfit = config.FlipOnlyWhenInProfit, - UseSynthApi = config.UseSynthApi, - UseForPositionSizing = config.UseForPositionSizing, - UseForSignalFiltering = config.UseForSignalFiltering, - UseForDynamicStopLoss = config.UseForDynamicStopLoss - }; - } - - public static TradingBotConfig Map(TradingBotConfigDto dto) - { - if (dto == null) - return null; - - return new TradingBotConfig - { - AccountName = dto.AccountName, - MoneyManagement = Map(dto.MoneyManagement), - Ticker = dto.Ticker, - Timeframe = dto.Timeframe, - IsForWatchingOnly = dto.IsForWatchingOnly, - BotTradingBalance = dto.BotTradingBalance, - IsForBacktest = dto.IsForBacktest, - CooldownPeriod = dto.CooldownPeriod, - MaxLossStreak = dto.MaxLossStreak, - FlipPosition = dto.FlipPosition, - Name = dto.Name, - RiskManagement = Map(dto.RiskManagement), - Scenario = Map(dto.Scenario), - ScenarioName = dto.ScenarioName, - MaxPositionTimeHours = dto.MaxPositionTimeHours, - CloseEarlyWhenProfitable = dto.CloseEarlyWhenProfitable, - FlipOnlyWhenInProfit = dto.FlipOnlyWhenInProfit, - UseSynthApi = dto.UseSynthApi, - UseForPositionSizing = dto.UseForPositionSizing, - UseForSignalFiltering = dto.UseForSignalFiltering, - UseForDynamicStopLoss = dto.UseForDynamicStopLoss - }; - } - - #endregion - - #region RiskManagement - - public static RiskManagementDto Map(RiskManagement riskManagement) - { - if (riskManagement == null) - return null; - - return new RiskManagementDto - { - AdverseProbabilityThreshold = riskManagement.AdverseProbabilityThreshold, - FavorableProbabilityThreshold = riskManagement.FavorableProbabilityThreshold, - RiskAversion = riskManagement.RiskAversion, - KellyMinimumThreshold = riskManagement.KellyMinimumThreshold, - KellyMaximumCap = riskManagement.KellyMaximumCap, - MaxLiquidationProbability = riskManagement.MaxLiquidationProbability, - SignalValidationTimeHorizonHours = riskManagement.SignalValidationTimeHorizonHours, - PositionMonitoringTimeHorizonHours = riskManagement.PositionMonitoringTimeHorizonHours, - PositionWarningThreshold = riskManagement.PositionWarningThreshold, - PositionAutoCloseThreshold = riskManagement.PositionAutoCloseThreshold, - KellyFractionalMultiplier = riskManagement.KellyFractionalMultiplier, - RiskTolerance = riskManagement.RiskTolerance, - UseExpectedUtility = riskManagement.UseExpectedUtility, - UseKellyCriterion = riskManagement.UseKellyCriterion - }; - } - - public static RiskManagement Map(RiskManagementDto dto) - { - if (dto == null) - return null; - - return new RiskManagement - { - AdverseProbabilityThreshold = dto.AdverseProbabilityThreshold, - FavorableProbabilityThreshold = dto.FavorableProbabilityThreshold, - RiskAversion = dto.RiskAversion, - KellyMinimumThreshold = dto.KellyMinimumThreshold, - KellyMaximumCap = dto.KellyMaximumCap, - MaxLiquidationProbability = dto.MaxLiquidationProbability, - SignalValidationTimeHorizonHours = dto.SignalValidationTimeHorizonHours, - PositionMonitoringTimeHorizonHours = dto.PositionMonitoringTimeHorizonHours, - PositionWarningThreshold = dto.PositionWarningThreshold, - PositionAutoCloseThreshold = dto.PositionAutoCloseThreshold, - KellyFractionalMultiplier = dto.KellyFractionalMultiplier, - RiskTolerance = dto.RiskTolerance, - UseExpectedUtility = dto.UseExpectedUtility, - UseKellyCriterion = dto.UseKellyCriterion - }; - } - - #endregion - - #region BundleBacktestRequests - - public static BundleBacktestRequestDto Map(BundleBacktestRequest domain) - { - if (domain == null) return null; - return new BundleBacktestRequestDto - { - RequestId = domain.RequestId, - User = Map(domain.User), - CompletedAt = domain.CompletedAt, - Status = domain.Status, - BacktestRequestsJson = domain.BacktestRequestsJson, - TotalBacktests = domain.TotalBacktests, - CompletedBacktests = domain.CompletedBacktests, - FailedBacktests = domain.FailedBacktests, - ErrorMessage = domain.ErrorMessage, - ProgressInfo = domain.ProgressInfo, - CurrentBacktest = domain.CurrentBacktest, - EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds, - Name = domain.Name, - Results = domain.Results - }; - } - - public static BundleBacktestRequest Map(BundleBacktestRequestDto dto) - { - if (dto == null) return null; - return new BundleBacktestRequest - { - RequestId = dto.RequestId, - User = Map(dto.User), - CreatedAt = dto.CreatedAt, - CompletedAt = dto.CompletedAt, - Status = dto.Status, - BacktestRequestsJson = dto.BacktestRequestsJson, - TotalBacktests = dto.TotalBacktests, - CompletedBacktests = dto.CompletedBacktests, - FailedBacktests = dto.FailedBacktests, - ErrorMessage = dto.ErrorMessage, - ProgressInfo = dto.ProgressInfo, - CurrentBacktest = dto.CurrentBacktest, - EstimatedTimeRemainingSeconds = dto.EstimatedTimeRemainingSeconds, - Name = dto.Name, - Results = dto.Results - }; - } - - #endregion -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs deleted file mode 100644 index 850d3bb..0000000 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System.Linq.Expressions; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Attributes; -using Managing.Infrastructure.Databases.MongoDb.Configurations; -using MongoDB.Bson; -using MongoDB.Driver; - -namespace Managing.Infrastructure.Databases.MongoDb -{ - public class MongoRepository : IMongoRepository - where TDocument : IDocument - { - private readonly IMongoCollection _collection; - private readonly IMongoDatabase _database; - - public MongoRepository(IManagingDatabaseSettings settings) - { - _database = new MongoClient(settings.ConnectionString).GetDatabase(settings.DatabaseName); - _collection = _database.GetCollection(GetCollectionName(typeof(TDocument))); - } - - private protected string GetCollectionName(Type documentType) - { - return ((BsonCollectionAttribute)documentType.GetCustomAttributes( - typeof(BsonCollectionAttribute), - true) - .FirstOrDefault())?.CollectionName; - } - - public virtual IQueryable AsQueryable() - { - return _collection.AsQueryable(); - } - - public virtual IEnumerable FilterBy( - Expression> filterExpression) - { - return _collection.Find(filterExpression).ToEnumerable(); - } - - public virtual IEnumerable FilterBy(FilterDefinition filter) - { - return _collection.Find(filter).ToEnumerable(); - } - - public virtual IEnumerable FindAll() - { - return _collection.Find(_ => true).ToEnumerable(); - } - - public virtual IEnumerable FilterBy( - Expression> filterExpression, - Expression> projectionExpression) - { - return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable(); - } - - public virtual TDocument FindOne(Expression> filterExpression) - { - return _collection.Find(filterExpression).FirstOrDefault(); - } - - public virtual Task FindOneAsync(Expression> filterExpression) - { - return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync()); - } - - public virtual TDocument FindById(string id) - { - var objectId = new ObjectId(id); - var filter = Builders.Filter.Eq(doc => doc.Id, objectId); - return _collection.Find(filter).SingleOrDefault(); - } - - public virtual Task FindByIdAsync(string id) - { - return Task.Run(() => - { - var objectId = new ObjectId(id); - var filter = Builders.Filter.Eq(doc => doc.Id, objectId); - return _collection.Find(filter).SingleOrDefaultAsync(); - }); - } - - public virtual void InsertOne(TDocument document) - { - _collection.InsertOne(document); - } - - public virtual Task InsertOneAsync(TDocument document) - { - return Task.Run(() => _collection.InsertOneAsync(document)); - } - - public void InsertMany(ICollection documents) - { - _collection.InsertMany(documents); - } - - public void DropCollection() - { - _database.DropCollection(GetCollectionName(typeof(TDocument))); - } - - public virtual async Task InsertManyAsync(ICollection documents) - { - await _collection.InsertManyAsync(documents); - } - - public void ReplaceOne(TDocument document) - { - var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); - _collection.FindOneAndReplace(filter, document); - } - - public virtual async Task ReplaceOneAsync(TDocument document) - { - var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); - await _collection.FindOneAndReplaceAsync(filter, document); - } - - public virtual void Update(TDocument entity) - { - if (entity.Id == ObjectId.Empty) - { - entity.Id = ObjectId.GenerateNewId(); - } - - var option = new ReplaceOptions { IsUpsert = true }; - _collection.ReplaceOne(x => entity != null && x.Id == entity.Id, entity, option); - } - - public virtual void DeleteOne(Expression> filterExpression) - { - _collection.FindOneAndDelete(filterExpression); - } - - public virtual Task DeleteOneAsync(Expression> filterExpression) - { - return Task.Run(() => _collection.FindOneAndDeleteAsync(filterExpression)); - } - - public virtual void DeleteById(string id) - { - var objectId = new ObjectId(id); - var filter = Builders.Filter.Eq(doc => doc.Id, objectId); - _collection.FindOneAndDelete(filter); - } - - public virtual Task DeleteByIdAsync(string id) - { - return Task.Run(() => - { - var objectId = new ObjectId(id); - var filter = Builders.Filter.Eq(doc => doc.Id, objectId); - _collection.FindOneAndDeleteAsync(filter); - }); - } - - public virtual void DeleteMany(Expression> filterExpression) - { - _collection.DeleteMany(filterExpression); - } - - public virtual Task DeleteManyAsync(Expression> filterExpression) - { - return _collection.DeleteManyAsync(filterExpression); - } - - public virtual void CreateIndex(string column) - { - var keys = Builders.IndexKeys.Ascending(column); - var indexOptions = new CreateIndexOptions { Unique = true }; - var model = new CreateIndexModel(keys, indexOptions); - _collection.Indexes.CreateOne(model); - } - - public IMongoCollection GetCollection() - { - return _collection; - } - } -} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz deleted file mode 100644 index cb79d13..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.metadata.json.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.metadata.json.gz deleted file mode 100644 index cf75afd..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.metadata.json.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.bson.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.bson.gz deleted file mode 100644 index eb949a5..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.bson.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.metadata.json.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.metadata.json.gz deleted file mode 100644 index c98ef8b..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.metadata.json.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.bson.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.bson.gz deleted file mode 100644 index 03393fe..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.bson.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz deleted file mode 100644 index 743fc31..0000000 Binary files a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz and /dev/null differ diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Configurations/PostgreSqlSettings.cs b/src/Managing.Infrastructure.Database/PostgreSql/Configurations/PostgreSqlSettings.cs new file mode 100644 index 0000000..a89a6a7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Configurations/PostgreSqlSettings.cs @@ -0,0 +1,11 @@ +namespace Managing.Infrastructure.Databases.PostgreSql.Configurations; + +public class PostgreSqlSettings : IPostgreSqlSettings +{ + public string ConnectionString { get; set; } = string.Empty; +} + +public interface IPostgreSqlSettings +{ + string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/DesignTimeDbContextFactory.cs b/src/Managing.Infrastructure.Database/PostgreSql/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..a166362 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/DesignTimeDbContextFactory.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public ManagingDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + // Use a default connection string for design-time migrations + // This should match your local PostgreSQL setup + optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"); + + return new ManagingDbContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs new file mode 100644 index 0000000..d28993b --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs @@ -0,0 +1,20 @@ +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +public class AccountEntity +{ + public int Id { get; set; } + public string Name { get; set; } + public TradingExchanges Exchange { get; set; } + public AccountType Type { get; set; } + public string? Key { get; set; } + public string? Secret { get; set; } + public int? UserId { get; set; } + + // Navigation properties + public UserEntity? User { get; set; } + + // Store balances as JSON if needed, or skip them entirely + // public string? BalancesJson { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs new file mode 100644 index 0000000..857969a --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs @@ -0,0 +1,83 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Backtests")] +public class BacktestEntity +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Identifier { get; set; } = string.Empty; + + [Required] + [MaxLength(255)] + public string RequestId { get; set; } = string.Empty; + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal FinalPnl { get; set; } + + [Required] + public int WinRate { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal GrowthPercentage { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal HodlPercentage { get; set; } + + [Required] + [Column(TypeName = "jsonb")] + public string ConfigJson { get; set; } = string.Empty; + + [Required] + [Column(TypeName = "jsonb")] + public string PositionsJson { get; set; } = string.Empty; + + [Required] + [Column(TypeName = "jsonb")] + public string SignalsJson { get; set; } = string.Empty; + + [Required] + public DateTime StartDate { get; set; } + + [Required] + public DateTime EndDate { get; set; } + + [Required] + [Column(TypeName = "jsonb")] + public string MoneyManagementJson { get; set; } = string.Empty; + + [Required] + [MaxLength(255)] + public string UserName { get; set; } = string.Empty; + + [Column(TypeName = "jsonb")] + public string? StatisticsJson { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal Fees { get; set; } + + [Required] + public double Score { get; set; } + + [Column(TypeName = "text")] + public string ScoreMessage { get; set; } = string.Empty; + + [Column(TypeName = "text")] + public string? Metadata { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + [Required] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotBackupEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotBackupEntity.cs new file mode 100644 index 0000000..fc2fc01 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotBackupEntity.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("BotBackups")] +public class BotBackupEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Identifier { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + public int? UserId { get; set; } + + // Navigation properties + [ForeignKey("UserId")] + public UserEntity? User { get; set; } + + /// + /// Bot configuration and state data stored as JSON string + /// + [Column(TypeName = "text")] + public string Data { get; set; } + + public BotStatus LastStatus { get; set; } + + public DateTime CreateDate { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs new file mode 100644 index 0000000..9f2c495 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs @@ -0,0 +1,70 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Managing.Domain.Backtests; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("BundleBacktestRequests")] +public class BundleBacktestRequestEntity +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string RequestId { get; set; } = string.Empty; + + [Required] + [MaxLength(255)] + public string UserName { get; set; } = string.Empty; + + // Foreign key to User entity + public int? UserId { get; set; } + + // Navigation property to User entity + public UserEntity? User { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? CompletedAt { get; set; } + + [Required] + public BundleBacktestRequestStatus Status { get; set; } + + [Required] + [Column(TypeName = "text")] + public string BacktestRequestsJson { get; set; } = string.Empty; + + [Required] + public int TotalBacktests { get; set; } + + [Required] + public int CompletedBacktests { get; set; } + + [Required] + public int FailedBacktests { get; set; } + + [Column(TypeName = "text")] + public string? ErrorMessage { get; set; } + + [Column(TypeName = "text")] + public string? ProgressInfo { get; set; } + + [MaxLength(500)] + public string? CurrentBacktest { get; set; } + + public int? EstimatedTimeRemainingSeconds { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } = string.Empty; + + [Required] + [Column(TypeName = "jsonb")] + public string ResultsJson { get; set; } = "[]"; + + [Required] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/FundingRateEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/FundingRateEntity.cs new file mode 100644 index 0000000..76a60dd --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/FundingRateEntity.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("FundingRates")] +public class FundingRateEntity +{ + [Key] + public int Id { get; set; } + + public Ticker Ticker { get; set; } + public TradingExchanges Exchange { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Rate { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal OpenInterest { get; set; } + + public DateTime Date { get; set; } + public TradeDirection Direction { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs new file mode 100644 index 0000000..0820ac5 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs @@ -0,0 +1,38 @@ +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +public class GeneticRequestEntity +{ + public int Id { get; set; } + public string RequestId { get; set; } + public int? UserId { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? CompletedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public string Status { get; set; } // GeneticRequestStatus as string + public Ticker Ticker { get; set; } + public Timeframe Timeframe { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public decimal Balance { get; set; } + public int PopulationSize { get; set; } + public int Generations { get; set; } + public double MutationRate { get; set; } + public GeneticSelectionMethod SelectionMethod { get; set; } + public GeneticCrossoverMethod CrossoverMethod { get; set; } + public GeneticMutationMethod MutationMethod { get; set; } + public int ElitismPercentage { get; set; } + public double MaxTakeProfit { get; set; } + public string? EligibleIndicatorsJson { get; set; } // Store List as JSON + public double? BestFitness { get; set; } + public string? BestIndividual { get; set; } + public string? ErrorMessage { get; set; } + public string? ProgressInfo { get; set; } + public string? BestChromosome { get; set; } + public double? BestFitnessSoFar { get; set; } + public int CurrentGeneration { get; set; } + + // Navigation properties + public UserEntity? User { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs new file mode 100644 index 0000000..6b69c3b --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Indicators")] +public class IndicatorEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + public IndicatorType Type { get; set; } + public Timeframe Timeframe { get; set; } + public SignalType SignalType { get; set; } + + public int MinimumHistory { get; set; } + public int? Period { get; set; } + public int? FastPeriods { get; set; } + public int? SlowPeriods { get; set; } + public int? SignalPeriods { get; set; } + public double? Multiplier { get; set; } + public int? StochPeriods { get; set; } + public int? SmoothPeriods { get; set; } + public int? CyclePeriods { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // Navigation property for the many-to-many relationship with scenarios + public virtual ICollection ScenarioIndicators { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/MoneyManagementEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/MoneyManagementEntity.cs new file mode 100644 index 0000000..33554d0 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/MoneyManagementEntity.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("MoneyManagements")] +public class MoneyManagementEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + [Required] + public Timeframe Timeframe { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal StopLoss { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal TakeProfit { get; set; } + + [Required] + [Column(TypeName = "decimal(18,8)")] + public decimal Leverage { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + public int? UserId { get; set; } + + // Navigation properties + [ForeignKey("UserId")] + public UserEntity? User { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs new file mode 100644 index 0000000..42e4e70 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Positions")] +public class PositionEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Identifier { get; set; } + + public DateTime Date { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal ProfitAndLoss { get; set; } + + public TradeDirection OriginDirection { get; set; } + public PositionStatus Status { get; set; } + public Ticker Ticker { get; set; } + public PositionInitiator Initiator { get; set; } + + [MaxLength(255)] + public string SignalIdentifier { get; set; } + + [MaxLength(255)] + public string AccountName { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + // Foreign keys to trades + public int? OpenTradeId { get; set; } + public int? StopLossTradeId { get; set; } + public int? TakeProfit1TradeId { get; set; } + public int? TakeProfit2TradeId { get; set; } + + // Money management data stored as JSON + [Column(TypeName = "text")] + public string? MoneyManagementJson { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // Navigation properties + [ForeignKey("OpenTradeId")] + public virtual TradeEntity? OpenTrade { get; set; } + + [ForeignKey("StopLossTradeId")] + public virtual TradeEntity? StopLossTrade { get; set; } + + [ForeignKey("TakeProfit1TradeId")] + public virtual TradeEntity? TakeProfit1Trade { get; set; } + + [ForeignKey("TakeProfit2TradeId")] + public virtual TradeEntity? TakeProfit2Trade { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs new file mode 100644 index 0000000..f0bf250 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Scenarios")] +public class ScenarioEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + public int LoopbackPeriod { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + // Navigation property for the many-to-many relationship with indicators + public virtual ICollection ScenarioIndicators { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioIndicatorEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioIndicatorEntity.cs new file mode 100644 index 0000000..2dfd3cb --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioIndicatorEntity.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("ScenarioIndicators")] +public class ScenarioIndicatorEntity +{ + [Key] + public int Id { get; set; } + + public int ScenarioId { get; set; } + public int IndicatorId { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + // Navigation properties + [ForeignKey("ScenarioId")] + public virtual ScenarioEntity Scenario { get; set; } = null!; + + [ForeignKey("IndicatorId")] + public virtual IndicatorEntity Indicator { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs new file mode 100644 index 0000000..dcc747d --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Signals")] +public class SignalEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Identifier { get; set; } + + public TradeDirection Direction { get; set; } + public Confidence Confidence { get; set; } + public DateTime Date { get; set; } + public Ticker Ticker { get; set; } + public SignalStatus Status { get; set; } + public Timeframe Timeframe { get; set; } + public IndicatorType Type { get; set; } + public SignalType SignalType { get; set; } + + [MaxLength(255)] + public string IndicatorName { get; set; } + + [MaxLength(255)] + public string? UserName { get; set; } + + // Candle data stored as JSON + [Column(TypeName = "text")] + public string? CandleJson { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SpotlightOverviewEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SpotlightOverviewEntity.cs new file mode 100644 index 0000000..35bd65f --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SpotlightOverviewEntity.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("SpotlightOverviews")] +public class SpotlightOverviewEntity +{ + [Key] + public int Id { get; set; } + + public Guid Identifier { get; set; } + public DateTime DateTime { get; set; } + public int ScenarioCount { get; set; } + + /// + /// JSON column containing the complex nested spotlights data + /// This stores the List as JSON to avoid complex normalization + /// + [Column(TypeName = "jsonb")] + public string SpotlightsJson { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthMinersLeaderboardEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthMinersLeaderboardEntity.cs new file mode 100644 index 0000000..6872139 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthMinersLeaderboardEntity.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +public class SynthMinersLeaderboardEntity +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(32)] + public string Asset { get; set; } + + [Required] + public int TimeIncrement { get; set; } + + public DateTime? SignalDate { get; set; } + + [Required] + public bool IsBacktest { get; set; } + + [Column(TypeName = "jsonb")] + public string MinersData { get; set; } // JSON serialized List + + [Required] + [MaxLength(255)] + public string CacheKey { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthPredictionEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthPredictionEntity.cs new file mode 100644 index 0000000..eeca416 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SynthPredictionEntity.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +public class SynthPredictionEntity +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(32)] + public string Asset { get; set; } + + [Required] + public int MinerUid { get; set; } + + [Required] + public int TimeIncrement { get; set; } + + [Required] + public int TimeLength { get; set; } + + public DateTime? SignalDate { get; set; } + + [Required] + public bool IsBacktest { get; set; } + + [Column(TypeName = "jsonb")] + public string PredictionData { get; set; } // JSON serialized MinerPrediction + + [Required] + [MaxLength(255)] + public string CacheKey { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/TopVolumeTickerEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TopVolumeTickerEntity.cs new file mode 100644 index 0000000..3749542 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TopVolumeTickerEntity.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("TopVolumeTickers")] +public class TopVolumeTickerEntity +{ + [Key] + public int Id { get; set; } + + public Ticker Ticker { get; set; } + public DateTime Date { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Volume { get; set; } + + public int Rank { get; set; } + public TradingExchanges Exchange { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs new file mode 100644 index 0000000..719d12b --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Trades")] +public class TradeEntity +{ + [Key] + public int Id { get; set; } + + public DateTime Date { get; set; } + public TradeDirection Direction { get; set; } + public TradeStatus Status { get; set; } + public TradeType TradeType { get; set; } + public Ticker Ticker { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Fee { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Quantity { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Price { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Leverage { get; set; } + + [MaxLength(255)] + public string? ExchangeOrderId { get; set; } + + [Column(TypeName = "text")] + public string? Message { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/TraderEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TraderEntity.cs new file mode 100644 index 0000000..e469375 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TraderEntity.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Traders")] +public class TraderEntity +{ + [Key] + public int Id { get; set; } + + [Required] + [MaxLength(255)] + public string Address { get; set; } + + public int Winrate { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Pnl { get; set; } + + public int TradeCount { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal AverageWin { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal AverageLoss { get; set; } + + [Column(TypeName = "decimal(18,8)")] + public decimal Roi { get; set; } + + /// + /// Indicates whether this is a best trader (true) or bad trader (false) + /// This allows us to use one table for both types + /// + public bool IsBestTrader { get; set; } + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs new file mode 100644 index 0000000..679e4de --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs @@ -0,0 +1,10 @@ +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +public class UserEntity +{ + public int Id { get; set; } + public string Name { get; set; } + public string? AgentName { get; set; } + public string? AvatarUrl { get; set; } + public string? TelegramChannel { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/WorkerEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/WorkerEntity.cs new file mode 100644 index 0000000..9016e58 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/WorkerEntity.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql.Entities; + +[Table("Workers")] +public class WorkerEntity +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public WorkerType WorkerType { get; set; } + + [Required] + public DateTime StartTime { get; set; } + + public DateTime? LastRunTime { get; set; } + + [Required] + public int ExecutionCount { get; set; } + + [Required] + public long DelayTicks { get; set; } // TimeSpan is not supported, store as ticks + + [Required] + public bool IsActive { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs new file mode 100644 index 0000000..9d9d53b --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs @@ -0,0 +1,521 @@ +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class ManagingDbContext : DbContext +{ + public ManagingDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Accounts { get; set; } + public DbSet Users { get; set; } + public DbSet GeneticRequests { get; set; } + public DbSet Backtests { get; set; } + public DbSet BundleBacktestRequests { get; set; } + + // Trading entities + public DbSet Scenarios { get; set; } + public DbSet Indicators { get; set; } + public DbSet ScenarioIndicators { get; set; } + public DbSet Signals { get; set; } + public DbSet Positions { get; set; } + public DbSet Trades { get; set; } + + + // Statistics entities + public DbSet TopVolumeTickers { get; set; } + public DbSet SpotlightOverviews { get; set; } + public DbSet Traders { get; set; } + public DbSet FundingRates { get; set; } + + // Bot entities + public DbSet BotBackups { get; set; } + + // Settings entities + public DbSet MoneyManagements { get; set; } + + // Worker entities + public DbSet Workers { get; set; } + + public DbSet SynthMinersLeaderboards { get; set; } + public DbSet SynthPredictions { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configure Account entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.Key).HasMaxLength(500); + entity.Property(e => e.Secret).HasMaxLength(500); + entity.Property(e => e.Exchange) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.Type) + .IsRequired() + .HasConversion(); // Store enum as string + + // Create unique index on account name + entity.HasIndex(e => e.Name).IsUnique(); + entity.HasIndex(e => e.Key); + + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // Configure User entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.AgentName).HasMaxLength(255); + entity.Property(e => e.AvatarUrl).HasMaxLength(500); + entity.Property(e => e.TelegramChannel).HasMaxLength(255); + }); + + // Configure GeneticRequest entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255); + entity.Property(e => e.Status).IsRequired().HasMaxLength(50); + entity.Property(e => e.Ticker) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.Timeframe) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.SelectionMethod) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.CrossoverMethod) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.MutationMethod) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.Balance).HasColumnType("decimal(18,8)"); + entity.Property(e => e.BestIndividual).HasMaxLength(4000); + entity.Property(e => e.ErrorMessage).HasMaxLength(2000); + entity.Property(e => e.ProgressInfo).HasMaxLength(4000); + entity.Property(e => e.BestChromosome).HasMaxLength(4000); + entity.Property(e => e.EligibleIndicatorsJson).HasMaxLength(2000); + + // Create indexes + entity.HasIndex(e => e.RequestId).IsUnique(); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.CreatedAt); + + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // Configure Backtest entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); + entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).IsRequired().HasMaxLength(255); + entity.Property(e => e.FinalPnl).HasColumnType("decimal(18,8)"); + entity.Property(e => e.GrowthPercentage).HasColumnType("decimal(18,8)"); + entity.Property(e => e.HodlPercentage).HasColumnType("decimal(18,8)"); + entity.Property(e => e.Fees).HasColumnType("decimal(18,8)"); + entity.Property(e => e.ConfigJson).HasColumnType("jsonb"); + entity.Property(e => e.PositionsJson).HasColumnType("jsonb"); + entity.Property(e => e.SignalsJson).HasColumnType("jsonb"); + entity.Property(e => e.MoneyManagementJson).HasColumnType("jsonb"); + entity.Property(e => e.StatisticsJson).HasColumnType("jsonb"); + entity.Property(e => e.ScoreMessage).HasMaxLength(1000); + entity.Property(e => e.Metadata).HasColumnType("text"); + + // Create indexes for common queries + entity.HasIndex(e => e.Identifier).IsUnique(); + entity.HasIndex(e => e.RequestId); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.Score); + entity.HasIndex(e => e.FinalPnl); + entity.HasIndex(e => e.WinRate); + entity.HasIndex(e => e.StartDate); + entity.HasIndex(e => e.EndDate); + entity.HasIndex(e => e.CreatedAt); + + // Composite indexes for efficient pagination and filtering + entity.HasIndex(e => new { e.UserName, e.Score }); + entity.HasIndex(e => new { e.RequestId, e.Score }); + }); + + // Configure BundleBacktestRequest entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).IsRequired().HasMaxLength(255); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.Status) + .IsRequired() + .HasConversion(); // Store enum as string + entity.Property(e => e.BacktestRequestsJson).HasColumnType("text"); + entity.Property(e => e.ErrorMessage).HasColumnType("text"); + entity.Property(e => e.ProgressInfo).HasColumnType("text"); + entity.Property(e => e.CurrentBacktest).HasMaxLength(500); + entity.Property(e => e.ResultsJson).HasColumnType("jsonb"); + + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + + // Create indexes for common queries + entity.HasIndex(e => e.RequestId).IsUnique(); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.CreatedAt); + entity.HasIndex(e => e.CompletedAt); + entity.HasIndex(e => e.UserId); + + // Composite index for user queries ordered by creation date + entity.HasIndex(e => new { e.UserName, e.CreatedAt }); + }); + + // Configure Scenario entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).HasMaxLength(255); + + // Create indexes + entity.HasIndex(e => e.Name); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.CreatedAt); + + // Composite index for user scenarios + entity.HasIndex(e => new { e.UserName, e.Name }); + }); + + // Configure Indicator entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.Type).IsRequired().HasConversion(); + entity.Property(e => e.Timeframe).IsRequired().HasConversion(); + entity.Property(e => e.SignalType).IsRequired().HasConversion(); + entity.Property(e => e.UserName).HasMaxLength(255); + + // Create indexes + entity.HasIndex(e => e.Name); + entity.HasIndex(e => e.Type); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.CreatedAt); + + // Composite index for user indicators + entity.HasIndex(e => new { e.UserName, e.Name }); + }); + + // Configure ScenarioIndicator junction table + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + + // Configure relationships + entity.HasOne(e => e.Scenario) + .WithMany(s => s.ScenarioIndicators) + .HasForeignKey(e => e.ScenarioId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.Indicator) + .WithMany(i => i.ScenarioIndicators) + .HasForeignKey(e => e.IndicatorId) + .OnDelete(DeleteBehavior.Cascade); + + // Create indexes + entity.HasIndex(e => e.ScenarioId); + entity.HasIndex(e => e.IndicatorId); + entity.HasIndex(e => new { e.ScenarioId, e.IndicatorId }).IsUnique(); + }); + + // Configure Signal entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); + entity.Property(e => e.Direction).IsRequired().HasConversion(); + entity.Property(e => e.Confidence).IsRequired().HasConversion(); + entity.Property(e => e.Ticker).IsRequired().HasConversion(); + entity.Property(e => e.Status).IsRequired().HasConversion(); + entity.Property(e => e.Timeframe).IsRequired().HasConversion(); + entity.Property(e => e.Type).IsRequired().HasConversion(); + entity.Property(e => e.SignalType).IsRequired().HasConversion(); + entity.Property(e => e.IndicatorName).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).HasMaxLength(255); + entity.Property(e => e.CandleJson).HasColumnType("text"); + + // Create indexes + entity.HasIndex(e => e.Identifier); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.Date); + entity.HasIndex(e => e.Ticker); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.CreatedAt); + + // Composite indexes for common queries + entity.HasIndex(e => new { e.UserName, e.Date }); + entity.HasIndex(e => new { e.Identifier, e.Date, e.UserName }).IsUnique(); + }); + + // Configure Position entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); + entity.Property(e => e.ProfitAndLoss).HasColumnType("decimal(18,8)"); + entity.Property(e => e.OriginDirection).IsRequired().HasConversion(); + entity.Property(e => e.Status).IsRequired().HasConversion(); + entity.Property(e => e.Ticker).IsRequired().HasConversion(); + entity.Property(e => e.Initiator).IsRequired().HasConversion(); + entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255); + entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).HasMaxLength(255); + entity.Property(e => e.MoneyManagementJson).HasColumnType("text"); + + // Configure relationships with trades + entity.HasOne(e => e.OpenTrade) + .WithMany() + .HasForeignKey(e => e.OpenTradeId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(e => e.StopLossTrade) + .WithMany() + .HasForeignKey(e => e.StopLossTradeId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(e => e.TakeProfit1Trade) + .WithMany() + .HasForeignKey(e => e.TakeProfit1TradeId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(e => e.TakeProfit2Trade) + .WithMany() + .HasForeignKey(e => e.TakeProfit2TradeId) + .OnDelete(DeleteBehavior.SetNull); + + // Create indexes + entity.HasIndex(e => e.Identifier).IsUnique(); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.Initiator); + entity.HasIndex(e => e.Date); + entity.HasIndex(e => e.CreatedAt); + + // Composite indexes + entity.HasIndex(e => new { e.UserName, e.Identifier }); + }); + + // Configure Trade entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Direction).IsRequired().HasConversion(); + entity.Property(e => e.Status).IsRequired().HasConversion(); + entity.Property(e => e.TradeType).IsRequired().HasConversion(); + entity.Property(e => e.Ticker).IsRequired().HasConversion(); + entity.Property(e => e.Fee).HasColumnType("decimal(18,8)"); + entity.Property(e => e.Quantity).HasColumnType("decimal(18,8)"); + entity.Property(e => e.Price).HasColumnType("decimal(18,8)"); + entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)"); + entity.Property(e => e.ExchangeOrderId).HasMaxLength(255); + entity.Property(e => e.Message).HasColumnType("text"); + + // Create indexes + entity.HasIndex(e => e.Date); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.ExchangeOrderId); + entity.HasIndex(e => e.CreatedAt); + }); + + + + // Configure TopVolumeTicker entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Volume).HasPrecision(18, 8); + + // Create indexes + entity.HasIndex(e => e.Ticker); + entity.HasIndex(e => e.Date); + entity.HasIndex(e => e.Exchange); + entity.HasIndex(e => e.Rank); + + // Composite indexes for efficient queries + entity.HasIndex(e => new { e.Exchange, e.Date }); + entity.HasIndex(e => new { e.Date, e.Rank }); + }); + + // Configure SpotlightOverview entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Identifier).IsRequired(); + entity.Property(e => e.SpotlightsJson).HasColumnType("jsonb"); + + // Create indexes + entity.HasIndex(e => e.Identifier).IsUnique(); + entity.HasIndex(e => e.DateTime); + entity.HasIndex(e => e.ScenarioCount); + + // Composite index for efficient queries + entity.HasIndex(e => new { e.DateTime, e.ScenarioCount }); + }); + + // Configure Trader entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Address).IsRequired().HasMaxLength(255); + entity.Property(e => e.Pnl).HasPrecision(18, 8); + entity.Property(e => e.AverageWin).HasPrecision(18, 8); + entity.Property(e => e.AverageLoss).HasPrecision(18, 8); + entity.Property(e => e.Roi).HasPrecision(18, 8); + + // Create indexes + entity.HasIndex(e => e.Address); + entity.HasIndex(e => e.IsBestTrader); + entity.HasIndex(e => e.Winrate); + entity.HasIndex(e => e.Pnl); + entity.HasIndex(e => e.Roi); + + // Composite indexes for efficient queries + entity.HasIndex(e => new { e.IsBestTrader, e.Winrate }); + entity.HasIndex(e => new { e.IsBestTrader, e.Roi }); + entity.HasIndex(e => new { e.Address, e.IsBestTrader }).IsUnique(); + }); + + // Configure FundingRate entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Rate).HasPrecision(18, 8); + entity.Property(e => e.OpenInterest).HasPrecision(18, 8); + + // Create indexes + entity.HasIndex(e => e.Ticker); + entity.HasIndex(e => e.Exchange); + entity.HasIndex(e => e.Date); + entity.HasIndex(e => e.Direction); + + // Composite indexes for efficient queries + entity.HasIndex(e => new { e.Ticker, e.Exchange }); + entity.HasIndex(e => new { e.Exchange, e.Date }); + entity.HasIndex(e => new { e.Ticker, e.Exchange, e.Date }).IsUnique(); + }); + + // Configure BotBackup entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); + entity.Property(e => e.UserName).HasMaxLength(255); + entity.Property(e => e.Data).IsRequired().HasColumnType("text"); + + // Create indexes + entity.HasIndex(e => e.Identifier).IsUnique(); + entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.LastStatus); + entity.HasIndex(e => e.CreateDate); + + // Composite index for user bots + entity.HasIndex(e => new { e.UserName, e.CreateDate }); + + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // Configure MoneyManagement entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.Timeframe).IsRequired().HasConversion(); + entity.Property(e => e.StopLoss).HasColumnType("decimal(18,8)"); + entity.Property(e => e.TakeProfit).HasColumnType("decimal(18,8)"); + entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)"); + entity.Property(e => e.UserName).HasMaxLength(255); + + // Create indexes + entity.HasIndex(e => e.Name); + entity.HasIndex(e => e.UserName); + + // Composite index for user money managements + entity.HasIndex(e => new { e.UserName, e.Name }); + + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // Configure Worker entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.WorkerType).IsRequired().HasConversion(); + entity.Property(e => e.StartTime).IsRequired(); + entity.Property(e => e.LastRunTime); + entity.Property(e => e.ExecutionCount).IsRequired(); + entity.Property(e => e.DelayTicks).IsRequired(); + entity.Property(e => e.IsActive).IsRequired(); + entity.HasIndex(e => e.WorkerType).IsUnique(); + }); + + // Configure SynthMinersLeaderboard entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Asset).IsRequired().HasMaxLength(32); + entity.Property(e => e.TimeIncrement).IsRequired(); + entity.Property(e => e.SignalDate); + entity.Property(e => e.IsBacktest).IsRequired(); + entity.Property(e => e.MinersData).HasColumnType("jsonb"); + entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255); + entity.Property(e => e.CreatedAt).IsRequired(); + entity.HasIndex(e => e.CacheKey).IsUnique(); + entity.HasIndex(e => e.CreatedAt); + }); + + // Configure SynthPrediction entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Asset).IsRequired().HasMaxLength(32); + entity.Property(e => e.MinerUid).IsRequired(); + entity.Property(e => e.TimeIncrement).IsRequired(); + entity.Property(e => e.TimeLength).IsRequired(); + entity.Property(e => e.SignalDate); + entity.Property(e => e.IsBacktest).IsRequired(); + entity.Property(e => e.PredictionData).HasColumnType("jsonb"); + entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255); + entity.Property(e => e.CreatedAt).IsRequired(); + entity.HasIndex(e => e.CacheKey).IsUnique(); + entity.HasIndex(e => e.CreatedAt); + }); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs new file mode 100644 index 0000000..ff1c8a8 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs @@ -0,0 +1,159 @@ +using System.Data; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Domain.Accounts; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlAccountRepository : IAccountRepository +{ + private readonly ManagingDbContext _context; + private readonly ICacheService _cacheService; + + public PostgreSqlAccountRepository(ManagingDbContext context, ICacheService cacheService) + { + _context = context; + _cacheService = cacheService; + } + + /// + /// Ensures the database connection is open before executing queries + /// + private async Task EnsureConnectionOpenAsync() + { + if (_context.Database.GetDbConnection().State != ConnectionState.Open) + { + await _context.Database.OpenConnectionAsync(); + } + } + + /// + /// Safely closes the database connection if it was opened by us + /// + private async Task SafeCloseConnectionAsync() + { + if (_context.Database.GetDbConnection().State == ConnectionState.Open) + { + await _context.Database.CloseConnectionAsync(); + } + } + + public void DeleteAccountByName(string name) + { + var accountEntity = _context.Accounts + .AsTracking() // Explicitly enable tracking for delete operations + .FirstOrDefault(a => a.Name == name); + if (accountEntity != null) + { + _context.Accounts.Remove(accountEntity); + _context.SaveChanges(); + } + } + + public async Task GetAccountByKeyAsync(string key) + { + try + { + await EnsureConnectionOpenAsync(); + + var accountEntity = await _context.Accounts + .AsNoTracking() + .Include(a => a.User) + .FirstOrDefaultAsync(a => a.Key == key) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(accountEntity); + } + catch (Exception) + { + // If there's an error, try to reset the connection + await SafeCloseConnectionAsync(); + throw; + } + } + + public async Task GetAccountByNameAsync(string name) + { + try + { + var cacheKey = $"account_{name}"; + var cachedAccount = _cacheService.GetValue(cacheKey); + if (cachedAccount != null) + { + return cachedAccount; + } + + var accountEntity = await _context.Accounts + .AsNoTracking() + .Include(a => a.User) + .FirstOrDefaultAsync(a => a.Name == name) + .ConfigureAwait(false); + + var account = PostgreSqlMappers.Map(accountEntity); + _cacheService.SaveValue(cacheKey, account, TimeSpan.FromHours(1)); + return account; + } + catch (Exception ex) + { + // If there's an error, try to reset the connection + throw; + } + finally + { + } + } + + public async Task> GetAccountsAsync() + { + try + { + await EnsureConnectionOpenAsync(); + + // Use proper async operations with AsNoTracking for optimal performance + var accountEntities = await _context.Accounts + .AsNoTracking() + .Include(a => a.User) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(accountEntities); + } + catch (Exception) + { + // If there's an error, try to reset the connection + await SafeCloseConnectionAsync(); + throw; + } + } + + public async Task InsertAccountAsync(Account account) + { + var accountEntity = PostgreSqlMappers.Map(account); + + // Handle User relationship - check if user exists or create new one + if (account.User != null) + { + var existingUser = await _context.Users + .AsTracking() // Explicitly enable tracking for this operation + .FirstOrDefaultAsync(u => u.Name == account.User.Name) + .ConfigureAwait(false); + + if (existingUser != null) + { + accountEntity.UserId = existingUser.Id; + accountEntity.User = null; // Prevent EF from trying to insert duplicate user + } + else + { + // Let EF handle the new user creation + accountEntity.UserId = null; + } + } + + // Balances are not stored in PostgreSQL, they remain in domain logic only + + _context.Accounts.Add(accountEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs new file mode 100644 index 0000000..389b495 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs @@ -0,0 +1,730 @@ +using System.Diagnostics; +using Exilion.TradingAtomics; +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Backtests; +using Managing.Domain.Bots; +using Managing.Domain.Users; +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlBacktestRepository : IBacktestRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlBacktestRepository(ManagingDbContext context) + { + _context = context; + } + + // User-specific operations + public void InsertBacktestForUser(User user, Backtest result) + { + ValidateBacktestData(result); + result.User = user; + + var entity = PostgreSqlMappers.Map(result); + _context.Backtests.Add(entity); + _context.SaveChanges(); + } + + /// + /// Validates that all numeric fields in the backtest are of the correct type + /// + private void ValidateBacktestData(Backtest backtest) + { + // Ensure FinalPnl is a valid decimal + if (backtest.FinalPnl.GetType() != typeof(decimal)) + { + throw new InvalidOperationException( + $"FinalPnl must be of type decimal, but got {backtest.FinalPnl.GetType().Name}"); + } + + // Ensure other numeric fields are correct + if (backtest.GrowthPercentage.GetType() != typeof(decimal)) + { + throw new InvalidOperationException( + $"GrowthPercentage must be of type decimal, but got {backtest.GrowthPercentage.GetType().Name}"); + } + + if (backtest.HodlPercentage.GetType() != typeof(decimal)) + { + throw new InvalidOperationException( + $"HodlPercentage must be of type decimal, but got {backtest.HodlPercentage.GetType().Name}"); + } + + if (backtest.Score.GetType() != typeof(double)) + { + throw new InvalidOperationException( + $"Score must be of type double, but got {backtest.Score.GetType().Name}"); + } + + if (backtest.WinRate.GetType() != typeof(int)) + { + throw new InvalidOperationException( + $"WinRate must be of type int, but got {backtest.WinRate.GetType().Name}"); + } + } + + public IEnumerable GetBacktestsByUser(User user) + { + var entities = _context.Backtests + .AsNoTracking() + .Where(b => b.UserName == user.Name) + .ToList(); + + return entities.Select(PostgreSqlMappers.Map); + } + + public async Task> GetBacktestsByUserAsync(User user) + { + var entities = await _context.Backtests + .AsNoTracking() + .Where(b => b.UserName == user.Name) + .ToListAsync() + .ConfigureAwait(false); + + return entities.Select(PostgreSqlMappers.Map); + } + + public IEnumerable GetBacktestsByRequestId(string requestId) + { + var entities = _context.Backtests + .AsNoTracking() + .Where(b => b.RequestId == requestId) + .ToList(); + + return entities.Select(PostgreSqlMappers.Map); + } + + public async Task> GetBacktestsByRequestIdAsync(string requestId) + { + var entities = await _context.Backtests + .AsNoTracking() + .Where(b => b.RequestId == requestId) + .ToListAsync() + .ConfigureAwait(false); + + return entities.Select(PostgreSqlMappers.Map); + } + + public (IEnumerable Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, + int page, int pageSize, string sortBy = "score", string sortOrder = "desc") + { + var stopwatch = Stopwatch.StartNew(); + + var baseQuery = _context.Backtests + .AsNoTracking() + .Where(b => b.RequestId == requestId); + + var afterQueryMs = stopwatch.ElapsedMilliseconds; + var totalCount = baseQuery.Count(); + var afterCountMs = stopwatch.ElapsedMilliseconds; + + // Apply sorting + IQueryable sortedQuery = sortBy.ToLower() switch + { + "score" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score), + "finalpnl" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.FinalPnl) + : baseQuery.OrderBy(b => b.FinalPnl), + "winrate" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.WinRate) + : baseQuery.OrderBy(b => b.WinRate), + "growthpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.GrowthPercentage) + : baseQuery.OrderBy(b => b.GrowthPercentage), + "hodlpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.HodlPercentage) + : baseQuery.OrderBy(b => b.HodlPercentage), + _ => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score) + }; + + var afterSortMs = stopwatch.ElapsedMilliseconds; + var entities = sortedQuery + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); + var afterToListMs = stopwatch.ElapsedMilliseconds; + + Console.WriteLine( + $"[PostgreSqlBacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms"); + + var mappedBacktests = entities.Select(entity => new LightBacktest + { + Id = entity.Identifier, + Config = JsonConvert.DeserializeObject(entity.ConfigJson), + FinalPnl = entity.FinalPnl, + WinRate = entity.WinRate, + GrowthPercentage = entity.GrowthPercentage, + HodlPercentage = entity.HodlPercentage, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.MaxDrawdown + : null, + Fees = entity.Fees, + SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.SharpeRatio != null + ? (double?)JsonConvert.DeserializeObject(entity.StatisticsJson).SharpeRatio + : null + : null, + Score = entity.Score, + ScoreMessage = entity.ScoreMessage ?? string.Empty + }); + + return (mappedBacktests, totalCount); + } + + public async Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync( + string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc") + { + var stopwatch = Stopwatch.StartNew(); + + var baseQuery = _context.Backtests + .AsNoTracking() + .Where(b => b.RequestId == requestId); + + var afterQueryMs = stopwatch.ElapsedMilliseconds; + var totalCount = await baseQuery.CountAsync().ConfigureAwait(false); + var afterCountMs = stopwatch.ElapsedMilliseconds; + + // Apply sorting + IQueryable sortedQuery = sortBy.ToLower() switch + { + "score" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score), + "finalpnl" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.FinalPnl) + : baseQuery.OrderBy(b => b.FinalPnl), + "winrate" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.WinRate) + : baseQuery.OrderBy(b => b.WinRate), + "growthpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.GrowthPercentage) + : baseQuery.OrderBy(b => b.GrowthPercentage), + "hodlpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.HodlPercentage) + : baseQuery.OrderBy(b => b.HodlPercentage), + _ => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score) + }; + + var afterSortMs = stopwatch.ElapsedMilliseconds; + var entities = await sortedQuery + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync() + .ConfigureAwait(false); + var afterToListMs = stopwatch.ElapsedMilliseconds; + + Console.WriteLine( + $"[PostgreSqlBacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms"); + + var mappedBacktests = entities.Select(entity => new LightBacktest + { + Id = entity.Identifier, + Config = JsonConvert.DeserializeObject(entity.ConfigJson), + FinalPnl = entity.FinalPnl, + WinRate = entity.WinRate, + GrowthPercentage = entity.GrowthPercentage, + HodlPercentage = entity.HodlPercentage, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.MaxDrawdown + : null, + Fees = entity.Fees, + SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.SharpeRatio != null + ? (double?)JsonConvert.DeserializeObject(entity.StatisticsJson).SharpeRatio + : null + : null, + Score = entity.Score, + ScoreMessage = entity.ScoreMessage ?? string.Empty + }); + + return (mappedBacktests, totalCount); + } + + public Backtest GetBacktestByIdForUser(User user, string id) + { + var entity = _context.Backtests + .AsNoTracking() + .FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name); + + return entity != null ? PostgreSqlMappers.Map(entity) : null; + } + + public async Task GetBacktestByIdForUserAsync(User user, string id) + { + var entity = await _context.Backtests + .AsNoTracking() + .FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name) + .ConfigureAwait(false); + + return entity != null ? PostgreSqlMappers.Map(entity) : null; + } + + public void DeleteBacktestByIdForUser(User user, string id) + { + var entity = _context.Backtests + .AsTracking() + .FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name); + + if (entity != null) + { + _context.Backtests.Remove(entity); + _context.SaveChanges(); + } + } + + public async Task DeleteBacktestByIdForUserAsync(User user, string id) + { + var entity = await _context.Backtests + .AsTracking() + .FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name) + .ConfigureAwait(false); + + if (entity != null) + { + _context.Backtests.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public void DeleteBacktestsByIdsForUser(User user, IEnumerable ids) + { + var entities = _context.Backtests + .AsTracking() + .Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) + .ToList(); + + if (entities.Any()) + { + _context.Backtests.RemoveRange(entities); + _context.SaveChanges(); + } + } + + public async Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable ids) + { + var entities = await _context.Backtests + .AsTracking() + .Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) + .ToListAsync() + .ConfigureAwait(false); + + if (entities.Any()) + { + _context.Backtests.RemoveRange(entities); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public void DeleteAllBacktestsForUser(User user) + { + var entities = _context.Backtests + .AsTracking() + .Where(b => b.UserName == user.Name) + .ToList(); + + if (entities.Any()) + { + _context.Backtests.RemoveRange(entities); + _context.SaveChanges(); + } + } + + public void DeleteBacktestsByRequestId(string requestId) + { + var entities = _context.Backtests + .AsTracking() + .Where(b => b.RequestId == requestId) + .ToList(); + + if (entities.Any()) + { + _context.Backtests.RemoveRange(entities); + _context.SaveChanges(); + } + } + + public async Task DeleteBacktestsByRequestIdAsync(string requestId) + { + var entities = await _context.Backtests + .AsTracking() + .Where(b => b.RequestId == requestId) + .ToListAsync() + .ConfigureAwait(false); + + if (entities.Any()) + { + _context.Backtests.RemoveRange(entities); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public (IEnumerable Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, + int pageSize, string sortBy = "score", string sortOrder = "desc") + { + var stopwatch = Stopwatch.StartNew(); + + var baseQuery = _context.Backtests + .AsNoTracking() + .Where(b => b.UserName == user.Name); + + var afterQueryMs = stopwatch.ElapsedMilliseconds; + var totalCount = baseQuery.Count(); + var afterCountMs = stopwatch.ElapsedMilliseconds; + + // Apply sorting + IQueryable sortedQuery = sortBy.ToLower() switch + { + "score" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score), + "finalpnl" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.FinalPnl) + : baseQuery.OrderBy(b => b.FinalPnl), + "winrate" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.WinRate) + : baseQuery.OrderBy(b => b.WinRate), + "growthpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.GrowthPercentage) + : baseQuery.OrderBy(b => b.GrowthPercentage), + "hodlpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.HodlPercentage) + : baseQuery.OrderBy(b => b.HodlPercentage), + _ => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score) + }; + + var afterSortMs = stopwatch.ElapsedMilliseconds; + var entities = sortedQuery + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); + var afterToListMs = stopwatch.ElapsedMilliseconds; + + Console.WriteLine( + $"[PostgreSqlBacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms"); + + var mappedBacktests = entities.Select(entity => new LightBacktest + { + Id = entity.Identifier, + Config = JsonConvert.DeserializeObject(entity.ConfigJson), + FinalPnl = entity.FinalPnl, + WinRate = entity.WinRate, + GrowthPercentage = entity.GrowthPercentage, + HodlPercentage = entity.HodlPercentage, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.MaxDrawdown + : null, + Fees = entity.Fees, + SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.SharpeRatio != null + ? (double?)JsonConvert.DeserializeObject(entity.StatisticsJson).SharpeRatio + : null + : null, + Score = entity.Score, + ScoreMessage = entity.ScoreMessage ?? string.Empty + }); + + return (mappedBacktests, totalCount); + } + + public async Task<(IEnumerable Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync( + User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc") + { + var stopwatch = Stopwatch.StartNew(); + + var baseQuery = _context.Backtests + .AsNoTracking() + .Where(b => b.UserName == user.Name); + + var afterQueryMs = stopwatch.ElapsedMilliseconds; + var totalCount = await baseQuery.CountAsync().ConfigureAwait(false); + var afterCountMs = stopwatch.ElapsedMilliseconds; + + // Apply sorting + IQueryable sortedQuery = sortBy.ToLower() switch + { + "score" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score), + "finalpnl" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.FinalPnl) + : baseQuery.OrderBy(b => b.FinalPnl), + "winrate" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.WinRate) + : baseQuery.OrderBy(b => b.WinRate), + "growthpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.GrowthPercentage) + : baseQuery.OrderBy(b => b.GrowthPercentage), + "hodlpercentage" => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.HodlPercentage) + : baseQuery.OrderBy(b => b.HodlPercentage), + _ => sortOrder == "desc" + ? baseQuery.OrderByDescending(b => b.Score) + : baseQuery.OrderBy(b => b.Score) + }; + + var afterSortMs = stopwatch.ElapsedMilliseconds; + var entities = await sortedQuery + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync() + .ConfigureAwait(false); + var afterToListMs = stopwatch.ElapsedMilliseconds; + + Console.WriteLine( + $"[PostgreSqlBacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms"); + + var mappedBacktests = entities.Select(entity => new LightBacktest + { + Id = entity.Identifier, + Config = JsonConvert.DeserializeObject(entity.ConfigJson), + FinalPnl = entity.FinalPnl, + WinRate = entity.WinRate, + GrowthPercentage = entity.GrowthPercentage, + HodlPercentage = entity.HodlPercentage, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.MaxDrawdown + : null, + Fees = entity.Fees, + SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson)?.SharpeRatio != null + ? (double?)JsonConvert.DeserializeObject(entity.StatisticsJson).SharpeRatio + : null + : null, + Score = entity.Score, + ScoreMessage = entity.ScoreMessage ?? string.Empty + }); + + return (mappedBacktests, totalCount); + } + + // Bundle backtest methods + public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest) + { + bundleRequest.User = user; + var entity = PostgreSqlMappers.Map(bundleRequest); + + // Set the UserId by finding the user entity + var userEntity = _context.Users.FirstOrDefault(u => u.Name == user.Name); + if (userEntity != null) + { + entity.UserId = userEntity.Id; + } + + _context.BundleBacktestRequests.Add(entity); + _context.SaveChanges(); + } + + public async Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest) + { + bundleRequest.User = user; + var entity = PostgreSqlMappers.Map(bundleRequest); + + // Set the UserId by finding the user entity + var userEntity = await _context.Users.FirstOrDefaultAsync(u => u.Name == user.Name); + if (userEntity != null) + { + entity.UserId = userEntity.Id; + } + + await _context.BundleBacktestRequests.AddAsync(entity); + await _context.SaveChangesAsync(); + } + + public IEnumerable GetBundleBacktestRequestsByUser(User user) + { + var entities = _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .Where(b => b.UserName == user.Name) + .OrderByDescending(b => b.CreatedAt) + .ToList(); + + return entities.Select(PostgreSqlMappers.Map); + } + + public async Task> GetBundleBacktestRequestsByUserAsync(User user) + { + var entities = await _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .Where(b => b.UserName == user.Name) + .OrderByDescending(b => b.CreatedAt) + .ToListAsync() + .ConfigureAwait(false); + + return entities.Select(PostgreSqlMappers.Map); + } + + public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id) + { + var entity = _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name); + + return entity != null ? PostgreSqlMappers.Map(entity) : null; + } + + public async Task GetBundleBacktestRequestByIdForUserAsync(User user, string id) + { + var entity = await _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) + .ConfigureAwait(false); + + return entity != null ? PostgreSqlMappers.Map(entity) : null; + } + + public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest) + { + var entity = _context.BundleBacktestRequests + .AsTracking() + .FirstOrDefault(b => b.RequestId == bundleRequest.RequestId); + + if (entity != null) + { + // Update the entity properties + entity.Status = bundleRequest.Status; + entity.CompletedAt = bundleRequest.CompletedAt; + entity.CompletedBacktests = bundleRequest.CompletedBacktests; + entity.FailedBacktests = bundleRequest.FailedBacktests; + entity.ErrorMessage = bundleRequest.ErrorMessage; + entity.ProgressInfo = bundleRequest.ProgressInfo; + entity.CurrentBacktest = bundleRequest.CurrentBacktest; + entity.EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds; + entity.UpdatedAt = DateTime.UtcNow; + + // Serialize Results to JSON + if (bundleRequest.Results != null && bundleRequest.Results.Any()) + { + try + { + entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results); + } + catch + { + entity.ResultsJson = "[]"; + } + } + else + { + entity.ResultsJson = "[]"; + } + + _context.SaveChanges(); + } + } + + public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest) + { + var entity = await _context.BundleBacktestRequests + .AsTracking() + .FirstOrDefaultAsync(b => b.RequestId == bundleRequest.RequestId) + .ConfigureAwait(false); + + if (entity != null) + { + // Update the entity properties + entity.Status = bundleRequest.Status; + entity.CompletedAt = bundleRequest.CompletedAt; + entity.CompletedBacktests = bundleRequest.CompletedBacktests; + entity.FailedBacktests = bundleRequest.FailedBacktests; + entity.ErrorMessage = bundleRequest.ErrorMessage; + entity.ProgressInfo = bundleRequest.ProgressInfo; + entity.CurrentBacktest = bundleRequest.CurrentBacktest; + entity.EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds; + entity.UpdatedAt = DateTime.UtcNow; + + // Serialize Results to JSON + if (bundleRequest.Results != null && bundleRequest.Results.Any()) + { + try + { + entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results); + } + catch + { + entity.ResultsJson = "[]"; + } + } + else + { + entity.ResultsJson = "[]"; + } + + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public void DeleteBundleBacktestRequestByIdForUser(User user, string id) + { + var entity = _context.BundleBacktestRequests + .AsTracking() + .FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name); + + if (entity != null) + { + _context.BundleBacktestRequests.Remove(entity); + _context.SaveChanges(); + } + } + + public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id) + { + var entity = await _context.BundleBacktestRequests + .AsTracking() + .FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) + .ConfigureAwait(false); + + if (entity != null) + { + _context.BundleBacktestRequests.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public IEnumerable GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status) + { + var entities = _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .Where(b => b.Status == status) + .ToList(); + + return entities.Select(PostgreSqlMappers.Map); + } + + public async Task> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status) + { + var entities = await _context.BundleBacktestRequests + .AsNoTracking() + .Include(b => b.User) + .Where(b => b.Status == status) + .ToListAsync() + .ConfigureAwait(false); + + return entities.Select(PostgreSqlMappers.Map); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs new file mode 100644 index 0000000..02e9143 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs @@ -0,0 +1,91 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Bots; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlBotRepository : IBotRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlBotRepository(ManagingDbContext context) + { + _context = context; + } + + public async Task InsertBotAsync(BotBackup bot) + { + bot.CreateDate = DateTime.UtcNow; + var entity = PostgreSqlMappers.Map(bot); + // Set the UserId if user is provided + if (bot.User != null) + { + var userEntity = await _context.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Name == bot.User.Name) + .ConfigureAwait(false); + entity.UserId = userEntity?.Id; + } + + await _context.BotBackups.AddAsync(entity).ConfigureAwait(false); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task> GetBotsAsync() + { + var entities = await _context.BotBackups + .AsNoTracking() + .Include(m => m.User) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities); + } + + public async Task UpdateBackupBot(BotBackup bot) + { + var existingEntity = await _context.BotBackups + .AsTracking() + .FirstOrDefaultAsync(b => b.Identifier == bot.Identifier) + .ConfigureAwait(false); + + if (existingEntity == null) + { + throw new InvalidOperationException($"Bot backup with identifier '{bot.Identifier}' not found"); + } + + // Update the entity properties + existingEntity.Data = bot.SerializeData(); // Use the serialized data string + existingEntity.LastStatus = bot.LastStatus; + existingEntity.UpdatedAt = DateTime.UtcNow; + existingEntity.UserName = bot.User?.Name; + + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task DeleteBotBackup(string identifier) + { + var entity = await _context.BotBackups + .AsTracking() + .FirstOrDefaultAsync(b => b.Identifier == identifier) + .ConfigureAwait(false); + + if (entity == null) + { + throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found"); + } + + _context.BotBackups.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task GetBotByIdentifierAsync(string identifier) + { + var entity = await _context.BotBackups + .AsNoTracking() + .Include(m => m.User) + .FirstOrDefaultAsync(b => b.Identifier == identifier) + .ConfigureAwait(false); + return PostgreSqlMappers.Map(entity); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlGeneticRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlGeneticRepository.cs new file mode 100644 index 0000000..48e00ef --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlGeneticRepository.cs @@ -0,0 +1,156 @@ +using System.Text.Json; +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Backtests; +using Managing.Domain.Users; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlGeneticRepository : IGeneticRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlGeneticRepository(ManagingDbContext context) + { + _context = context; + } + + public void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest) + { + geneticRequest.User = user; + var geneticRequestEntity = PostgreSqlMappers.Map(geneticRequest); + + // Handle User relationship - check if user exists or create new one + if (user != null) + { + var existingUser = _context.Users + .AsTracking() // Explicitly enable tracking for this operation + .FirstOrDefault(u => u.Name == user.Name); + if (existingUser != null) + { + geneticRequestEntity.UserId = existingUser.Id; + geneticRequestEntity.User = null; // Prevent EF from trying to insert duplicate user + } + else + { + // Let EF handle the new user creation + geneticRequestEntity.UserId = null; + } + } + + _context.GeneticRequests.Add(geneticRequestEntity); + _context.SaveChanges(); + } + + public IEnumerable GetGeneticRequestsByUser(User user) + { + // Use synchronous operations and AsNoTracking to avoid concurrency issues + var geneticRequestEntities = _context.GeneticRequests + .AsNoTracking() + .Include(gr => gr.User) + .Where(gr => gr.User != null && gr.User.Name == user.Name) + .OrderByDescending(gr => gr.CreatedAt) + .ToList(); + + return PostgreSqlMappers.Map(geneticRequestEntities); + } + + public GeneticRequest GetGeneticRequestByIdForUser(User user, string id) + { + // Use synchronous operations and AsNoTracking to avoid concurrency issues + var geneticRequestEntity = _context.GeneticRequests + .AsNoTracking() + .Include(gr => gr.User) + .FirstOrDefault(gr => gr.RequestId == id); + + // Check if genetic request exists and belongs to the user + if (geneticRequestEntity != null && geneticRequestEntity.User != null && + geneticRequestEntity.User.Name == user.Name) + { + return PostgreSqlMappers.Map(geneticRequestEntity); + } + + return null; + } + + public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest) + { + var existingEntity = _context.GeneticRequests + .Include(gr => gr.User) + .FirstOrDefault(gr => gr.RequestId == geneticRequest.RequestId); + + if (existingEntity != null) + { + // Update the existing entity with new values + existingEntity.CompletedAt = geneticRequest.CompletedAt; + existingEntity.UpdatedAt = DateTime.UtcNow; + existingEntity.Status = geneticRequest.Status.ToString(); + existingEntity.BestFitness = geneticRequest.BestFitness; + existingEntity.BestIndividual = geneticRequest.BestIndividual; + existingEntity.ErrorMessage = geneticRequest.ErrorMessage; + existingEntity.ProgressInfo = geneticRequest.ProgressInfo; + existingEntity.BestChromosome = geneticRequest.BestChromosome; + existingEntity.BestFitnessSoFar = geneticRequest.BestFitnessSoFar; + existingEntity.CurrentGeneration = geneticRequest.CurrentGeneration; + + // Update EligibleIndicators JSON + if (geneticRequest.EligibleIndicators != null && geneticRequest.EligibleIndicators.Any()) + { + try + { + existingEntity.EligibleIndicatorsJson = JsonSerializer.Serialize(geneticRequest.EligibleIndicators); + } + catch + { + existingEntity.EligibleIndicatorsJson = "[]"; + } + } + else + { + existingEntity.EligibleIndicatorsJson = "[]"; + } + + // Only update the tracked entity, do not attach a new one + await _context.SaveChangesAsync(); + } + } + + public void DeleteGeneticRequestByIdForUser(User user, string id) + { + var geneticRequestEntity = _context.GeneticRequests + .Include(gr => gr.User) + .FirstOrDefault(gr => gr.RequestId == id); + + if (geneticRequestEntity != null && geneticRequestEntity.User != null && + geneticRequestEntity.User.Name == user.Name) + { + _context.GeneticRequests.Remove(geneticRequestEntity); + _context.SaveChanges(); + } + } + + public void DeleteAllGeneticRequestsForUser(User user) + { + var geneticRequestEntities = _context.GeneticRequests + .Include(gr => gr.User) + .Where(gr => gr.User != null && gr.User.Name == user.Name) + .ToList(); + + if (geneticRequestEntities.Any()) + { + _context.GeneticRequests.RemoveRange(geneticRequestEntities); + _context.SaveChanges(); + } + } + + public async Task> GetGeneticRequestsAsync(GeneticRequestStatus status) + { + var requests = await _context.GeneticRequests + .AsNoTracking() + .Include(gr => gr.User) + .Where(gr => gr.Status == status.ToString()) + .OrderBy(gr => gr.CreatedAt) + .ToListAsync(); + return PostgreSqlMappers.Map(requests).ToList(); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs new file mode 100644 index 0000000..d5ea070 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs @@ -0,0 +1,916 @@ +using Exilion.TradingAtomics; +using Managing.Domain.Accounts; +using Managing.Domain.Backtests; +using Managing.Domain.Bots; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Managing.Domain.Statistics; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Domain.Users; +using Managing.Domain.Workers; +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +using SystemJsonSerializer = System.Text.Json.JsonSerializer; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public static class PostgreSqlMappers +{ + #region Account Mappings + + public static Account Map(AccountEntity entity) + { + if (entity == null) return null; + + return new Account + { + Name = entity.Name, + Exchange = entity.Exchange, + Type = entity.Type, + Key = entity.Key, + Secret = entity.Secret, + User = entity.User != null ? Map(entity.User) : null, + Balances = new List() // Empty list for now, balances handled separately if needed + }; + } + + public static AccountEntity Map(Account account) + { + if (account == null) return null; + + return new AccountEntity + { + Name = account.Name, + Exchange = account.Exchange, + Type = account.Type, + Key = account.Key, + Secret = account.Secret, + User = account.User != null ? Map(account.User) : null + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region MoneyManagement Mappings + + public static MoneyManagement Map(MoneyManagementEntity entity) + { + if (entity == null) return null; + + return new MoneyManagement + { + Name = entity.Name, + Timeframe = entity.Timeframe, + StopLoss = entity.StopLoss, + TakeProfit = entity.TakeProfit, + Leverage = entity.Leverage, + User = entity.User != null ? Map(entity.User) : null + }; + } + + public static MoneyManagementEntity Map(MoneyManagement moneyManagement) + { + if (moneyManagement == null) return null; + + return new MoneyManagementEntity + { + Name = moneyManagement.Name, + Timeframe = moneyManagement.Timeframe, + StopLoss = moneyManagement.StopLoss, + TakeProfit = moneyManagement.TakeProfit, + Leverage = moneyManagement.Leverage, + UserName = moneyManagement.User?.Name, + User = moneyManagement.User != null ? Map(moneyManagement.User) : null + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + public static MoneyManagementEntity Map(LightMoneyManagement lightMoneyManagement) + { + if (lightMoneyManagement == null) return null; + + return new MoneyManagementEntity + { + Name = lightMoneyManagement.Name, + Timeframe = lightMoneyManagement.Timeframe, + StopLoss = lightMoneyManagement.StopLoss, + TakeProfit = lightMoneyManagement.TakeProfit, + Leverage = lightMoneyManagement.Leverage + }; + } + + #endregion + + #region User Mappings + + public static User Map(UserEntity entity) + { + if (entity == null) return null; + + return new User + { + Name = entity.Name, + AgentName = entity.AgentName, + AvatarUrl = entity.AvatarUrl, + TelegramChannel = entity.TelegramChannel + }; + } + + public static UserEntity Map(User user) + { + if (user == null) return null; + + return new UserEntity + { + Name = user.Name, + AgentName = user.AgentName, + AvatarUrl = user.AvatarUrl, + TelegramChannel = user.TelegramChannel + }; + } + + #endregion + + #region GeneticRequest Mappings + + public static GeneticRequest Map(GeneticRequestEntity entity) + { + if (entity == null) return null; + + var geneticRequest = new GeneticRequest(entity.RequestId) + { + User = entity.User != null ? Map(entity.User) : null, + CreatedAt = entity.CreatedAt, + CompletedAt = entity.CompletedAt, + Status = Enum.Parse(entity.Status), + Ticker = entity.Ticker, + Timeframe = entity.Timeframe, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + Balance = entity.Balance, + PopulationSize = entity.PopulationSize, + Generations = entity.Generations, + MutationRate = entity.MutationRate, + SelectionMethod = entity.SelectionMethod, + CrossoverMethod = entity.CrossoverMethod, + MutationMethod = entity.MutationMethod, + ElitismPercentage = entity.ElitismPercentage, + MaxTakeProfit = entity.MaxTakeProfit, + BestFitness = entity.BestFitness, + BestIndividual = entity.BestIndividual, + ErrorMessage = entity.ErrorMessage, + ProgressInfo = entity.ProgressInfo, + BestChromosome = entity.BestChromosome, + BestFitnessSoFar = entity.BestFitnessSoFar, + CurrentGeneration = entity.CurrentGeneration + }; + + // Deserialize EligibleIndicators from JSON + if (!string.IsNullOrEmpty(entity.EligibleIndicatorsJson)) + { + try + { + geneticRequest.EligibleIndicators = SystemJsonSerializer.Deserialize>(entity.EligibleIndicatorsJson) ?? new List(); + } + catch + { + geneticRequest.EligibleIndicators = new List(); + } + } + + return geneticRequest; + } + + public static GeneticRequestEntity Map(GeneticRequest geneticRequest) + { + if (geneticRequest == null) return null; + + var entity = new GeneticRequestEntity + { + RequestId = geneticRequest.RequestId, + User = geneticRequest.User != null ? Map(geneticRequest.User) : null, + CreatedAt = geneticRequest.CreatedAt, + CompletedAt = geneticRequest.CompletedAt, + UpdatedAt = DateTime.UtcNow, + Status = geneticRequest.Status.ToString(), + Ticker = geneticRequest.Ticker, + Timeframe = geneticRequest.Timeframe, + StartDate = geneticRequest.StartDate, + EndDate = geneticRequest.EndDate, + Balance = geneticRequest.Balance, + PopulationSize = geneticRequest.PopulationSize, + Generations = geneticRequest.Generations, + MutationRate = geneticRequest.MutationRate, + SelectionMethod = geneticRequest.SelectionMethod, + CrossoverMethod = geneticRequest.CrossoverMethod, + MutationMethod = geneticRequest.MutationMethod, + ElitismPercentage = geneticRequest.ElitismPercentage, + MaxTakeProfit = geneticRequest.MaxTakeProfit, + BestFitness = geneticRequest.BestFitness, + BestIndividual = geneticRequest.BestIndividual, + ErrorMessage = geneticRequest.ErrorMessage, + ProgressInfo = geneticRequest.ProgressInfo, + BestChromosome = geneticRequest.BestChromosome, + BestFitnessSoFar = geneticRequest.BestFitnessSoFar, + CurrentGeneration = geneticRequest.CurrentGeneration + }; + + // Serialize EligibleIndicators to JSON + if (geneticRequest.EligibleIndicators != null && geneticRequest.EligibleIndicators.Any()) + { + try + { + entity.EligibleIndicatorsJson = SystemJsonSerializer.Serialize(geneticRequest.EligibleIndicators); + } + catch + { + entity.EligibleIndicatorsJson = "[]"; + } + } + else + { + entity.EligibleIndicatorsJson = "[]"; + } + + return entity; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region Backtest Mappings + + public static Backtest Map(BacktestEntity entity) + { + if (entity == null) return null; + + // Deserialize JSON fields using MongoMappers for compatibility + var config = JsonConvert.DeserializeObject(entity.ConfigJson); + var positions = JsonConvert.DeserializeObject>(entity.PositionsJson) ?? new List(); + var signals = JsonConvert.DeserializeObject>(entity.SignalsJson) ?? new List(); + var statistics = !string.IsNullOrEmpty(entity.StatisticsJson) + ? JsonConvert.DeserializeObject(entity.StatisticsJson) + : null; + + var backtest = new Backtest(config, positions, signals) + { + Id = entity.Identifier, + FinalPnl = entity.FinalPnl, + WinRate = entity.WinRate, + GrowthPercentage = entity.GrowthPercentage, + HodlPercentage = entity.HodlPercentage, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + User = new User { Name = entity.UserName }, + Statistics = statistics, + Fees = entity.Fees, + Score = entity.Score, + ScoreMessage = entity.ScoreMessage, + RequestId = entity.RequestId, + Metadata = entity.Metadata + }; + + return backtest; + } + + public static BacktestEntity Map(Backtest backtest) + { + if (backtest == null) return null; + + return new BacktestEntity + { + Identifier = backtest.Id, + RequestId = backtest.RequestId, + FinalPnl = backtest.FinalPnl, + WinRate = backtest.WinRate, + GrowthPercentage = backtest.GrowthPercentage, + HodlPercentage = backtest.HodlPercentage, + ConfigJson = JsonConvert.SerializeObject(backtest.Config), + PositionsJson = JsonConvert.SerializeObject(backtest.Positions), + SignalsJson = JsonConvert.SerializeObject(backtest.Signals), + StartDate = backtest.StartDate, + EndDate = backtest.EndDate, + MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement), + UserName = backtest.User?.Name ?? string.Empty, + StatisticsJson = backtest.Statistics != null ? JsonConvert.SerializeObject(backtest.Statistics) : null, + Fees = backtest.Fees, + Score = backtest.Score, + ScoreMessage = backtest.ScoreMessage ?? string.Empty, + Metadata = backtest.Metadata?.ToString(), + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region BundleBacktestRequest Mappings + + public static BundleBacktestRequest Map(BundleBacktestRequestEntity entity) + { + if (entity == null) return null; + + var bundleRequest = new BundleBacktestRequest(entity.RequestId) + { + User = entity.User != null ? Map(entity.User) : new User { Name = entity.UserName }, + CreatedAt = entity.CreatedAt, + CompletedAt = entity.CompletedAt, + Status = entity.Status, + BacktestRequestsJson = entity.BacktestRequestsJson, + TotalBacktests = entity.TotalBacktests, + CompletedBacktests = entity.CompletedBacktests, + FailedBacktests = entity.FailedBacktests, + ErrorMessage = entity.ErrorMessage, + ProgressInfo = entity.ProgressInfo, + CurrentBacktest = entity.CurrentBacktest, + EstimatedTimeRemainingSeconds = entity.EstimatedTimeRemainingSeconds, + Name = entity.Name + }; + + // Deserialize Results from JSON + if (!string.IsNullOrEmpty(entity.ResultsJson)) + { + try + { + bundleRequest.Results = JsonConvert.DeserializeObject>(entity.ResultsJson) ?? new List(); + } + catch + { + bundleRequest.Results = new List(); + } + } + + return bundleRequest; + } + + public static BundleBacktestRequestEntity Map(BundleBacktestRequest bundleRequest) + { + if (bundleRequest == null) return null; + + var entity = new BundleBacktestRequestEntity + { + RequestId = bundleRequest.RequestId, + UserName = bundleRequest.User?.Name ?? string.Empty, + UserId = null, // Will be set by the repository when saving + CreatedAt = bundleRequest.CreatedAt, + CompletedAt = bundleRequest.CompletedAt, + Status = bundleRequest.Status, + BacktestRequestsJson = bundleRequest.BacktestRequestsJson, + TotalBacktests = bundleRequest.TotalBacktests, + CompletedBacktests = bundleRequest.CompletedBacktests, + FailedBacktests = bundleRequest.FailedBacktests, + ErrorMessage = bundleRequest.ErrorMessage, + ProgressInfo = bundleRequest.ProgressInfo, + CurrentBacktest = bundleRequest.CurrentBacktest, + EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds, + Name = bundleRequest.Name, + UpdatedAt = DateTime.UtcNow + }; + + // Serialize Results to JSON + if (bundleRequest.Results != null && bundleRequest.Results.Any()) + { + try + { + entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results); + } + catch + { + entity.ResultsJson = "[]"; + } + } + else + { + entity.ResultsJson = "[]"; + } + + return entity; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region Trading Mappings + + // Scenario mappings + public static Scenario Map(ScenarioEntity entity) + { + if (entity == null) return null; + + return new Scenario(entity.Name, entity.LoopbackPeriod) + { + User = entity.UserName != null ? new User { Name = entity.UserName } : null, + Indicators = new List() // Will be populated separately when needed + }; + } + + public static ScenarioEntity Map(Scenario scenario) + { + if (scenario == null) return null; + + return new ScenarioEntity + { + Name = scenario.Name, + LoopbackPeriod = scenario.LoopbackPeriod ?? 1, + UserName = scenario.User?.Name + }; + } + + // Indicator mappings + public static Indicator Map(IndicatorEntity entity) + { + if (entity == null) return null; + + return new Indicator(entity.Name, entity.Type) + { + SignalType = entity.SignalType, + MinimumHistory = entity.MinimumHistory, + Period = entity.Period, + FastPeriods = entity.FastPeriods, + SlowPeriods = entity.SlowPeriods, + SignalPeriods = entity.SignalPeriods, + Multiplier = entity.Multiplier, + SmoothPeriods = entity.SmoothPeriods, + StochPeriods = entity.StochPeriods, + CyclePeriods = entity.CyclePeriods, + User = entity.UserName != null ? new User { Name = entity.UserName } : null + }; + } + + public static IndicatorEntity Map(Indicator indicator) + { + if (indicator == null) return null; + + return new IndicatorEntity + { + Name = indicator.Name, + Type = indicator.Type, + Timeframe = Timeframe.FifteenMinutes, // Default timeframe + SignalType = indicator.SignalType, + MinimumHistory = indicator.MinimumHistory, + Period = indicator.Period, + FastPeriods = indicator.FastPeriods, + SlowPeriods = indicator.SlowPeriods, + SignalPeriods = indicator.SignalPeriods, + Multiplier = indicator.Multiplier, + SmoothPeriods = indicator.SmoothPeriods, + StochPeriods = indicator.StochPeriods, + CyclePeriods = indicator.CyclePeriods, + UserName = indicator.User?.Name + }; + } + + // Signal mappings + public static Signal Map(SignalEntity entity) + { + if (entity == null) return null; + + var candle = !string.IsNullOrEmpty(entity.CandleJson) + ? JsonConvert.DeserializeObject(entity.CandleJson) + : null; + + return new Signal( + entity.Ticker, + entity.Direction, + entity.Confidence, + candle, + entity.Date, + TradingExchanges.Evm, // Default exchange + entity.Type, + entity.SignalType, + entity.IndicatorName, + entity.UserName != null ? new User { Name = entity.UserName } : null) + { + Status = entity.Status + }; + } + + public static SignalEntity Map(Signal signal) + { + if (signal == null) return null; + + return new SignalEntity + { + Identifier = signal.Identifier, + Direction = signal.Direction, + Confidence = signal.Confidence, + Date = signal.Date, + Ticker = signal.Ticker, + Status = signal.Status, + Timeframe = signal.Timeframe, + Type = signal.IndicatorType, + SignalType = signal.SignalType, + IndicatorName = signal.IndicatorName, + UserName = signal.User?.Name, + CandleJson = signal.Candle != null ? JsonConvert.SerializeObject(signal.Candle) : null + }; + } + + // Position mappings + public static Position Map(PositionEntity entity) + { + if (entity == null) return null; + + // Deserialize money management + var moneyManagement = new MoneyManagement(); // Default money management + if (!string.IsNullOrEmpty(entity.MoneyManagementJson)) + { + moneyManagement = JsonConvert.DeserializeObject(entity.MoneyManagementJson) ?? new MoneyManagement(); + } + + var position = new Position( + entity.Identifier, + entity.AccountName, + entity.OriginDirection, + entity.Ticker, + moneyManagement, + entity.Initiator, + entity.Date, + entity.UserName != null ? new User { Name = entity.UserName } : null) + { + Status = entity.Status, + SignalIdentifier = entity.SignalIdentifier + }; + + // Set ProfitAndLoss with proper type + position.ProfitAndLoss = new ProfitAndLoss { Realized = entity.ProfitAndLoss }; + + // Map related trades + if (entity.OpenTrade != null) + position.Open = Map(entity.OpenTrade); + if (entity.StopLossTrade != null) + position.StopLoss = Map(entity.StopLossTrade); + if (entity.TakeProfit1Trade != null) + position.TakeProfit1 = Map(entity.TakeProfit1Trade); + if (entity.TakeProfit2Trade != null) + position.TakeProfit2 = Map(entity.TakeProfit2Trade); + + return position; + } + + public static PositionEntity Map(Position position) + { + if (position == null) return null; + + return new PositionEntity + { + Identifier = position.Identifier, + Date = position.Date, + ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0, + OriginDirection = position.OriginDirection, + Status = position.Status, + Ticker = position.Ticker, + Initiator = position.Initiator, + SignalIdentifier = position.SignalIdentifier, + AccountName = position.AccountName, + UserName = position.User?.Name, + MoneyManagementJson = position.MoneyManagement != null ? JsonConvert.SerializeObject(position.MoneyManagement) : null + }; + } + + // Trade mappings + public static Trade Map(TradeEntity entity) + { + if (entity == null) return null; + + return new Trade( + entity.Date, + entity.Direction, + entity.Status, + entity.TradeType, + entity.Ticker, + entity.Quantity, + entity.Price, + entity.Leverage, + entity.ExchangeOrderId, + entity.Message) + { + Fee = entity.Fee + }; + } + + public static TradeEntity Map(Trade trade) + { + if (trade == null) return null; + + return new TradeEntity + { + Date = trade.Date, + Direction = trade.Direction, + Status = trade.Status, + TradeType = trade.TradeType, + Ticker = trade.Ticker, + Fee = trade.Fee, + Quantity = trade.Quantity, + Price = trade.Price, + Leverage = trade.Leverage, + ExchangeOrderId = trade.ExchangeOrderId, + Message = trade.Message + }; + } + + + + // Collection mappings + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region Bot Mappings + + // BotBackup mappings + public static BotBackup Map(BotBackupEntity entity) + { + if (entity == null) return null; + + var botBackup = new BotBackup + { + Identifier = entity.Identifier, + User = entity.User != null ? Map(entity.User) : null, + LastStatus = entity.LastStatus, + CreateDate = entity.CreateDate + }; + + // Deserialize the JSON data using the helper method + botBackup.DeserializeData(entity.Data); + + return botBackup; + } + + public static BotBackupEntity Map(BotBackup botBackup) + { + if (botBackup == null) return null; + + return new BotBackupEntity + { + Identifier = botBackup.Identifier, + UserName = botBackup.User?.Name, + User = botBackup.User != null ? Map(botBackup.User) : null, + Data = botBackup.SerializeData(), // Serialize the data using the helper method + LastStatus = botBackup.LastStatus, + CreateDate = botBackup.CreateDate, + UpdatedAt = DateTime.UtcNow + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + public static IEnumerable Map(IEnumerable botBackups) + { + return botBackups?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region Statistics Mappings + + // TopVolumeTicker mappings + public static TopVolumeTicker Map(TopVolumeTickerEntity entity) + { + if (entity == null) return null; + + return new TopVolumeTicker + { + Ticker = entity.Ticker, + Date = entity.Date, + Volume = entity.Volume, + Rank = entity.Rank, + Exchange = entity.Exchange + }; + } + + public static TopVolumeTickerEntity Map(TopVolumeTicker topVolumeTicker) + { + if (topVolumeTicker == null) return null; + + return new TopVolumeTickerEntity + { + Ticker = topVolumeTicker.Ticker, + Date = topVolumeTicker.Date, + Volume = topVolumeTicker.Volume, + Rank = topVolumeTicker.Rank, + Exchange = topVolumeTicker.Exchange + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + // SpotlightOverview mappings + public static SpotlightOverview Map(SpotlightOverviewEntity entity) + { + if (entity == null) return null; + + var overview = new SpotlightOverview + { + Identifier = entity.Identifier, + DateTime = entity.DateTime, + ScenarioCount = entity.ScenarioCount, + Spotlights = new List() + }; + + // Deserialize the JSON spotlights data + if (!string.IsNullOrEmpty(entity.SpotlightsJson)) + { + try + { + overview.Spotlights = SystemJsonSerializer.Deserialize>(entity.SpotlightsJson) ?? new List(); + } + catch (JsonException) + { + // If deserialization fails, return empty list + overview.Spotlights = new List(); + } + } + + return overview; + } + + public static SpotlightOverviewEntity Map(SpotlightOverview overview) + { + if (overview == null) return null; + + var entity = new SpotlightOverviewEntity + { + Identifier = overview.Identifier, + DateTime = overview.DateTime, + ScenarioCount = overview.ScenarioCount + }; + + // Serialize the spotlights to JSON + if (overview.Spotlights != null) + { + entity.SpotlightsJson = SystemJsonSerializer.Serialize(overview.Spotlights); + } + + return entity; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + // Trader mappings + public static Trader Map(TraderEntity entity) + { + if (entity == null) return null; + + return new Trader + { + Address = entity.Address, + Winrate = entity.Winrate, + Pnl = entity.Pnl, + TradeCount = entity.TradeCount, + AverageWin = entity.AverageWin, + AverageLoss = entity.AverageLoss, + Roi = entity.Roi + }; + } + + public static TraderEntity Map(Trader trader, bool isBestTrader) + { + if (trader == null) return null; + + return new TraderEntity + { + Address = trader.Address, + Winrate = trader.Winrate, + Pnl = trader.Pnl, + TradeCount = trader.TradeCount, + AverageWin = trader.AverageWin, + AverageLoss = trader.AverageLoss, + Roi = trader.Roi, + IsBestTrader = isBestTrader + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + // FundingRate mappings + public static FundingRate Map(FundingRateEntity entity) + { + if (entity == null) return null; + + return new FundingRate + { + Ticker = entity.Ticker, + Exchange = entity.Exchange, + Rate = entity.Rate, + OpenInterest = entity.OpenInterest, + Date = entity.Date, + Direction = entity.Direction + }; + } + + public static FundingRateEntity Map(FundingRate fundingRate) + { + if (fundingRate == null) return null; + + return new FundingRateEntity + { + Ticker = fundingRate.Ticker, + Exchange = fundingRate.Exchange, + Rate = fundingRate.Rate, + OpenInterest = fundingRate.OpenInterest, + Date = fundingRate.Date, + Direction = fundingRate.Direction + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion + + #region Worker Mappings + + public static Worker Map(WorkerEntity entity) + { + if (entity == null) return null; + return new Worker + { + WorkerType = entity.WorkerType, + StartTime = entity.StartTime, + LastRunTime = entity.LastRunTime, + ExecutionCount = entity.ExecutionCount, + Delay = TimeSpan.FromTicks(entity.DelayTicks), + IsActive = entity.IsActive + }; + } + + public static WorkerEntity Map(Worker worker) + { + if (worker == null) return null; + return new WorkerEntity + { + WorkerType = worker.WorkerType, + StartTime = worker.StartTime, + LastRunTime = worker.LastRunTime, + ExecutionCount = worker.ExecutionCount, + DelayTicks = worker.Delay.Ticks, + IsActive = worker.IsActive + }; + } + + public static IEnumerable Map(IEnumerable entities) + { + return entities?.Select(Map) ?? Enumerable.Empty(); + } + + #endregion +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSettingsRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSettingsRepository.cs new file mode 100644 index 0000000..3773e46 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSettingsRepository.cs @@ -0,0 +1,173 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Users; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlSettingsRepository : ISettingsRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlSettingsRepository(ManagingDbContext context) + { + _context = context; + } + + public async Task DeleteMoneyManagementAsync(string name) + { + var moneyManagement = await _context.MoneyManagements + .AsTracking() + .FirstOrDefaultAsync(m => m.Name == name) + .ConfigureAwait(false); + + if (moneyManagement != null) + { + _context.MoneyManagements.Remove(moneyManagement); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public async Task DeleteMoneyManagementsAsync() + { + var moneyManagements = await _context.MoneyManagements + .AsTracking() + .ToListAsync() + .ConfigureAwait(false); + + _context.MoneyManagements.RemoveRange(moneyManagements); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task GetMoneyManagement(string name) + { + var moneyManagement = await _context.MoneyManagements + .AsNoTracking() + .Include(m => m.User) + .FirstOrDefaultAsync(m => m.Name == name) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(moneyManagement); + } + + public async Task> GetMoneyManagementsAsync() + { + var moneyManagements = await _context.MoneyManagements + .AsNoTracking() + .Include(m => m.User) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(moneyManagements); + } + + public async Task InsertMoneyManagement(LightMoneyManagement request, User user) + { + // Check if money management already exists for the same user + var existingMoneyManagement = await _context.MoneyManagements + .AsNoTracking() + .FirstOrDefaultAsync(m => m.Name == request.Name && + ((user == null && m.UserName == null) || + (user != null && m.UserName == user.Name))) + .ConfigureAwait(false); + + if (existingMoneyManagement != null) + { + throw new InvalidOperationException( + $"Money management with name '{request.Name}' already exists for user '{user?.Name}'"); + } + + var entity = PostgreSqlMappers.Map(request); + entity.UserName = user?.Name; + + // Set the UserId if user is provided + if (user != null) + { + var userEntity = await _context.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Name == user.Name) + .ConfigureAwait(false); + entity.UserId = userEntity?.Id; + } + + await _context.MoneyManagements.AddAsync(entity).ConfigureAwait(false); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user) + { + var entity = await _context.MoneyManagements + .AsTracking() + .FirstOrDefaultAsync(m => m.Name == moneyManagement.Name && + (user != null && m.UserName == user.Name)) + .ConfigureAwait(false); + + if (entity != null) + { + entity.Timeframe = moneyManagement.Timeframe; + entity.StopLoss = moneyManagement.StopLoss; + entity.TakeProfit = moneyManagement.TakeProfit; + entity.Leverage = moneyManagement.Leverage; + entity.UserName = user?.Name; + entity.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + // User-specific implementations + public async Task GetMoneyManagementByUser(User user, string name) + { + var moneyManagement = await _context.MoneyManagements + .AsNoTracking() + .Include(m => m.User) + .FirstOrDefaultAsync(m => + m.Name == name && + m.UserName != null && + m.UserName == user.Name) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(moneyManagement); + } + + public async Task> GetMoneyManagementsByUserAsync(User user) + { + var moneyManagements = await _context.MoneyManagements + .AsNoTracking() + .Include(m => m.User) + .Where(m => m.UserName != null && m.UserName == user.Name) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(moneyManagements); + } + + public async Task DeleteMoneyManagementByUserAsync(User user, string name) + { + var moneyManagement = await _context.MoneyManagements + .AsTracking() + .FirstOrDefaultAsync(m => + m.Name == name && + m.UserName != null && + m.UserName == user.Name) + .ConfigureAwait(false); + + if (moneyManagement != null) + { + _context.MoneyManagements.Remove(moneyManagement); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public async Task DeleteMoneyManagementsByUserAsync(User user) + { + var moneyManagements = await _context.MoneyManagements + .AsTracking() + .Where(m => m.UserName != null && m.UserName == user.Name) + .ToListAsync() + .ConfigureAwait(false); + + _context.MoneyManagements.RemoveRange(moneyManagements); + await _context.SaveChangesAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlStatisticRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlStatisticRepository.cs new file mode 100644 index 0000000..1c46256 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlStatisticRepository.cs @@ -0,0 +1,287 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Statistics; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlStatisticRepository : IStatisticRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlStatisticRepository(ManagingDbContext context) + { + _context = context; + } + + #region TopVolumeTicker Methods + + public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker) + { + var entity = PostgreSqlMappers.Map(topVolumeTicker); + await _context.TopVolumeTickers.AddAsync(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public IList GetTopVolumeTickers(DateTime date) + { + return GetTopVolumeTickersAsync(date).Result; + } + + public async Task> GetTopVolumeTickersAsync(DateTime date) + { + var entities = await _context.TopVolumeTickers + .AsNoTracking() + .Where(t => date < t.Date) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities).ToList(); + } + + #endregion + + #region SpotlightOverview Methods + + public async Task SaveSpotligthtOverview(SpotlightOverview overview) + { + var entity = PostgreSqlMappers.Map(overview); + await _context.SpotlightOverviews.AddAsync(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public IList GetSpotlightOverviews(DateTime date) + { + return GetSpotlightOverviewsAsync(date).Result; + } + + public async Task> GetSpotlightOverviewsAsync(DateTime date) + { + var entities = await _context.SpotlightOverviews + .AsNoTracking() + .Where(o => date.ToUniversalTime() < o.DateTime) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities).ToList(); + } + + public void UpdateSpotlightOverview(SpotlightOverview overview) + { + UpdateSpotlightOverviewAsync(overview).Wait(); + } + + public async Task UpdateSpotlightOverviewAsync(SpotlightOverview overview) + { + var existingEntity = await _context.SpotlightOverviews + .AsTracking() + .FirstOrDefaultAsync(o => o.Identifier == overview.Identifier) + .ConfigureAwait(false); + + if (existingEntity != null) + { + var updatedEntity = PostgreSqlMappers.Map(overview); + updatedEntity.Id = existingEntity.Id; // Keep the same ID + updatedEntity.CreatedAt = existingEntity.CreatedAt; // Keep original creation time + updatedEntity.UpdatedAt = DateTime.UtcNow; + + _context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + #endregion + + #region BestTrader Methods + + public List GetBestTraders() + { + return GetBestTradersAsync().Result; + } + + public async Task> GetBestTradersAsync() + { + var entities = await _context.Traders + .AsNoTracking() + .Where(t => t.IsBestTrader) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities).ToList(); + } + + public void UpdateBestTrader(Trader trader) + { + UpdateBestTraderAsync(trader).Wait(); + } + + public async Task UpdateBestTraderAsync(Trader trader) + { + var existingEntity = await _context.Traders + .AsTracking() + .FirstOrDefaultAsync(t => t.Address == trader.Address && t.IsBestTrader) + .ConfigureAwait(false); + + if (existingEntity != null) + { + var updatedEntity = PostgreSqlMappers.Map(trader, true); + updatedEntity.Id = existingEntity.Id; // Keep the same ID + updatedEntity.CreatedAt = existingEntity.CreatedAt; // Keep original creation time + updatedEntity.UpdatedAt = DateTime.UtcNow; + + _context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public async Task InsertBestTrader(Trader trader) + { + var entity = PostgreSqlMappers.Map(trader, true); + await _context.Traders.AddAsync(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task RemoveBestTrader(Trader trader) + { + var entity = await _context.Traders + .AsTracking() + .FirstOrDefaultAsync(t => t.Address == trader.Address && t.IsBestTrader) + .ConfigureAwait(false); + + if (entity != null) + { + _context.Traders.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + #endregion + + #region BadTrader Methods + + public List GetBadTraders() + { + return GetBadTradersAsync().Result; + } + + public async Task> GetBadTradersAsync() + { + var entities = await _context.Traders + .AsNoTracking() + .Where(t => !t.IsBestTrader) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities).ToList(); + } + + public void UpdateBadTrader(Trader trader) + { + UpdateBadTraderAsync(trader).Wait(); + } + + public async Task UpdateBadTraderAsync(Trader trader) + { + var existingEntity = await _context.Traders + .AsTracking() + .FirstOrDefaultAsync(t => t.Address == trader.Address && !t.IsBestTrader) + .ConfigureAwait(false); + + if (existingEntity != null) + { + var updatedEntity = PostgreSqlMappers.Map(trader, false); + updatedEntity.Id = existingEntity.Id; // Keep the same ID + updatedEntity.CreatedAt = existingEntity.CreatedAt; // Keep original creation time + updatedEntity.UpdatedAt = DateTime.UtcNow; + + _context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public async Task InsertBadTrader(Trader trader) + { + var entity = PostgreSqlMappers.Map(trader, false); + await _context.Traders.AddAsync(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task RemoveBadTrader(Trader trader) + { + var entity = await _context.Traders + .AsTracking() + .FirstOrDefaultAsync(t => t.Address == trader.Address && !t.IsBestTrader) + .ConfigureAwait(false); + + if (entity != null) + { + _context.Traders.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + #endregion + + #region FundingRate Methods + + public List GetFundingRates() + { + return GetFundingRatesAsync().Result; + } + + public async Task> GetFundingRatesAsync() + { + var entities = await _context.FundingRates + .AsNoTracking() + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(entities).ToList(); + } + + public async Task RemoveFundingRate(FundingRate oldRate) + { + var entity = await _context.FundingRates + .AsTracking() + .FirstOrDefaultAsync(r => r.Ticker == oldRate.Ticker && r.Exchange == oldRate.Exchange) + .ConfigureAwait(false); + + if (entity != null) + { + _context.FundingRates.Remove(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + public async Task InsertFundingRate(FundingRate newRate) + { + var entity = PostgreSqlMappers.Map(newRate); + await _context.FundingRates.AddAsync(entity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public void UpdateFundingRate(FundingRate oldRate, FundingRate newRate) + { + UpdateFundingRateAsync(oldRate, newRate).Wait(); + } + + public async Task UpdateFundingRateAsync(FundingRate oldRate, FundingRate newRate) + { + var existingEntity = await _context.FundingRates + .AsTracking() + .FirstOrDefaultAsync(r => r.Ticker == oldRate.Ticker && r.Exchange == oldRate.Exchange) + .ConfigureAwait(false); + + if (existingEntity != null) + { + var updatedEntity = PostgreSqlMappers.Map(newRate); + updatedEntity.Id = existingEntity.Id; // Keep the same ID + updatedEntity.CreatedAt = existingEntity.CreatedAt; // Keep original creation time + updatedEntity.UpdatedAt = DateTime.UtcNow; + + _context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + } + + #endregion +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSynthRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSynthRepository.cs new file mode 100644 index 0000000..9dda76d --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlSynthRepository.cs @@ -0,0 +1,223 @@ +using System.Text.Json; +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Synth.Models; +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlSynthRepository : ISynthRepository +{ + private readonly ManagingDbContext _context; + private readonly ILogger _logger; + + public PostgreSqlSynthRepository(ManagingDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task GetLeaderboardAsync(string cacheKey) + { + try + { + var entity = await _context.SynthMinersLeaderboards.AsNoTracking().FirstOrDefaultAsync(x => x.CacheKey == cacheKey); + if (entity == null) + { + _logger.LogDebug($"[PG Synth] No leaderboard cache found for key: {cacheKey}"); + return null; + } + var miners = JsonSerializer.Deserialize>(entity.MinersData); + return new SynthMinersLeaderboard + { + Id = entity.Id.ToString(), + Asset = entity.Asset, + TimeIncrement = entity.TimeIncrement, + SignalDate = entity.SignalDate, + IsBacktest = entity.IsBacktest, + Miners = miners ?? new List(), + CreatedAt = entity.CreatedAt + }; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error retrieving leaderboard cache for key: {cacheKey}"); + return null; + } + } + + public async Task SaveLeaderboardAsync(SynthMinersLeaderboard leaderboard) + { + try + { + leaderboard.CreatedAt = DateTime.UtcNow; + var cacheKey = leaderboard.GetCacheKey(); + var minersData = JsonSerializer.Serialize(leaderboard.Miners); + var existing = await _context.SynthMinersLeaderboards.FirstOrDefaultAsync(x => x.CacheKey == cacheKey); + if (existing != null) + { + existing.Asset = leaderboard.Asset; + existing.TimeIncrement = leaderboard.TimeIncrement; + existing.SignalDate = leaderboard.SignalDate; + existing.IsBacktest = leaderboard.IsBacktest; + existing.MinersData = minersData; + existing.CreatedAt = leaderboard.CreatedAt; + _context.SynthMinersLeaderboards.Update(existing); + _logger.LogDebug($"[PG Synth] Updated leaderboard in DB for key: {cacheKey}"); + } + else + { + var entity = new SynthMinersLeaderboardEntity + { + Id = Guid.NewGuid(), + Asset = leaderboard.Asset, + TimeIncrement = leaderboard.TimeIncrement, + SignalDate = leaderboard.SignalDate, + IsBacktest = leaderboard.IsBacktest, + MinersData = minersData, + CacheKey = cacheKey, + CreatedAt = leaderboard.CreatedAt + }; + await _context.SynthMinersLeaderboards.AddAsync(entity); + _logger.LogDebug($"[PG Synth] Saved new leaderboard to DB for key: {cacheKey}"); + } + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error saving leaderboard cache for key: {leaderboard.GetCacheKey()}"); + } + } + + public async Task> GetIndividualPredictionsAsync(string asset, int timeIncrement, int timeLength, List minerUids, bool isBacktest, DateTime? signalDate) + { + try + { + var results = new List(); + foreach (var minerUid in minerUids) + { + var cacheKey = $"{asset}_{timeIncrement}_{timeLength}_{minerUid}"; + if (isBacktest && signalDate.HasValue) + { + cacheKey += $"_backtest_{signalDate.Value:yyyy-MM-dd-HH}"; + } + var entity = await _context.SynthPredictions.AsNoTracking().FirstOrDefaultAsync(x => x.CacheKey == cacheKey); + if (entity != null) + { + var prediction = JsonSerializer.Deserialize(entity.PredictionData); + if (prediction != null) + { + results.Add(new SynthPrediction + { + Id = entity.Id.ToString(), + Asset = entity.Asset, + MinerUid = entity.MinerUid, + TimeIncrement = entity.TimeIncrement, + TimeLength = entity.TimeLength, + SignalDate = entity.SignalDate, + IsBacktest = entity.IsBacktest, + Prediction = prediction, + CreatedAt = entity.CreatedAt + }); + } + } + } + if (results.Any()) + { + _logger.LogDebug($"[PG Synth] Retrieved {results.Count}/{minerUids.Count} individual predictions for {asset}"); + } + else + { + _logger.LogDebug($"[PG Synth] No individual predictions found for {asset}"); + } + return results; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error retrieving individual predictions cache for asset: {asset}"); + return new List(); + } + } + + public async Task SaveIndividualPredictionAsync(SynthPrediction prediction) + { + try + { + prediction.CreatedAt = DateTime.UtcNow; + var cacheKey = prediction.GetCacheKey(); + var predictionData = JsonSerializer.Serialize(prediction.Prediction); + var existing = await _context.SynthPredictions.FirstOrDefaultAsync(x => x.CacheKey == cacheKey).ConfigureAwait(false); + if (existing != null) + { + existing.Asset = prediction.Asset; + existing.MinerUid = prediction.MinerUid; + existing.TimeIncrement = prediction.TimeIncrement; + existing.TimeLength = prediction.TimeLength; + existing.SignalDate = prediction.SignalDate; + existing.IsBacktest = prediction.IsBacktest; + existing.PredictionData = predictionData; + existing.CreatedAt = prediction.CreatedAt; + _context.SynthPredictions.Update(existing); + _logger.LogDebug($"[PG Synth] Updated individual prediction for miner {prediction.MinerUid}"); + } + else + { + var entity = new SynthPredictionEntity + { + Id = Guid.NewGuid(), + Asset = prediction.Asset, + MinerUid = prediction.MinerUid, + TimeIncrement = prediction.TimeIncrement, + TimeLength = prediction.TimeLength, + SignalDate = prediction.SignalDate, + IsBacktest = prediction.IsBacktest, + PredictionData = predictionData, + CacheKey = cacheKey, + CreatedAt = prediction.CreatedAt + }; + await _context.SynthPredictions.AddAsync(entity).ConfigureAwait(false); + _logger.LogDebug($"[PG Synth] Saved new individual prediction for miner {prediction.MinerUid}"); + } + await _context.SaveChangesAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error saving individual prediction cache for miner {prediction.MinerUid}: {ex.Message}"); + } + } + + public async Task SaveIndividualPredictionsAsync(List predictions) + { + if (!predictions.Any()) + return; + try + { + var saveTasks = predictions.Select(SaveIndividualPredictionAsync); + await Task.WhenAll(saveTasks); + _logger.LogInformation($"[PG Synth] Successfully saved {predictions.Count} individual predictions to DB"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error saving batch of {predictions.Count} individual predictions"); + } + } + + public async Task CleanupOldDataAsync(int retentionDays = 30) + { + try + { + var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays); + var oldLeaderboards = _context.SynthMinersLeaderboards.Where(x => x.CreatedAt < cutoffDate); + _context.SynthMinersLeaderboards.RemoveRange(oldLeaderboards); + var oldPredictions = _context.SynthPredictions.Where(x => x.CreatedAt < cutoffDate); + _context.SynthPredictions.RemoveRange(oldPredictions); + await _context.SaveChangesAsync(); + _logger.LogInformation($"[PG Synth] Cleaned up old Synth cache data older than {retentionDays} days"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error during cleanup of old Synth cache data"); + } + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs new file mode 100644 index 0000000..87b325d --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs @@ -0,0 +1,444 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Domain.Users; +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlTradingRepository : ITradingRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlTradingRepository(ManagingDbContext context) + { + _context = context; + } + + #region Scenario Methods + + public async Task DeleteScenarioAsync(string name) + { + var scenario = await _context.Scenarios.FirstOrDefaultAsync(s => s.Name == name); + if (scenario != null) + { + _context.Scenarios.Remove(scenario); + await _context.SaveChangesAsync(); + } + } + + public Scenario GetScenarioByName(string name) + { + return GetScenarioByNameAsync(name).Result; + } + + public async Task GetScenarioByNameAsync(string name) + { + var scenario = await _context.Scenarios + .AsNoTracking() + .Include(s => s.ScenarioIndicators) + .ThenInclude(si => si.Indicator) + .FirstOrDefaultAsync(s => s.Name == name) + .ConfigureAwait(false); + + if (scenario == null) return null; + + var mappedScenario = PostgreSqlMappers.Map(scenario); + // Map indicators from junction table + mappedScenario.Indicators = scenario.ScenarioIndicators + .Select(si => PostgreSqlMappers.Map(si.Indicator)) + .ToList(); + + return mappedScenario; + } + + public IEnumerable GetScenarios() + { + return GetScenariosAsync().Result; + } + + public async Task> GetScenariosAsync() + { + var scenarios = await _context.Scenarios + .AsNoTracking() + .Include(s => s.ScenarioIndicators) + .ThenInclude(si => si.Indicator) + .ToListAsync() + .ConfigureAwait(false); + + return scenarios.Select(scenario => + { + var mappedScenario = PostgreSqlMappers.Map(scenario); + mappedScenario.Indicators = scenario.ScenarioIndicators + .Select(si => PostgreSqlMappers.Map(si.Indicator)) + .ToList(); + return mappedScenario; + }); + } + + public async Task InsertScenarioAsync(Scenario scenario) + { + // Check if scenario already exists for the same user + var existingScenario = await _context.Scenarios + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Name == scenario.Name && + ((scenario.User == null && s.UserName == null) || + (scenario.User != null && s.UserName == scenario.User.Name))); + + if (existingScenario != null) + { + throw new InvalidOperationException( + $"Scenario with name '{scenario.Name}' already exists for user '{scenario.User?.Name}'"); + } + + var scenarioEntity = PostgreSqlMappers.Map(scenario); + _context.Scenarios.Add(scenarioEntity); + await _context.SaveChangesAsync(); + + // Handle scenario-indicator relationships + if (scenario.Indicators != null && scenario.Indicators.Any()) + { + foreach (var indicator in scenario.Indicators) + { + var indicatorEntity = await _context.Indicators + .AsNoTracking() + .FirstOrDefaultAsync(i => i.Name == indicator.Name && + ((indicator.User == null && i.UserName == null) || + (indicator.User != null && i.UserName == indicator.User.Name))); + + if (indicatorEntity != null) + { + var junction = new ScenarioIndicatorEntity + { + ScenarioId = scenarioEntity.Id, + IndicatorId = indicatorEntity.Id + }; + _context.ScenarioIndicators.Add(junction); + } + } + await _context.SaveChangesAsync(); + } + } + + public async Task UpdateScenarioAsync(Scenario scenario) + { + var entity = _context.Scenarios + .AsTracking() + .FirstOrDefault(s => s.Name == scenario.Name); + + if (entity != null) + { + entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1; + entity.UserName = scenario.User?.Name; + entity.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + } + } + + #endregion + + #region Indicator Methods + + public async Task DeleteIndicatorAsync(string name) + { + var indicator = _context.Indicators + .AsTracking() + .FirstOrDefault(i => i.Name == name); + + if (indicator != null) + { + _context.Indicators.Remove(indicator); + await _context.SaveChangesAsync(); + } + } + + public async Task DeleteIndicatorsAsync() + { + var indicators = _context.Indicators.AsTracking().ToList(); + _context.Indicators.RemoveRange(indicators); + await _context.SaveChangesAsync(); + } + + public async Task> GetIndicatorsAsync() + { + var indicators = await _context.Indicators + .AsNoTracking() + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(indicators); + } + + public async Task> GetStrategiesAsync() + { + var indicators = await _context.Indicators + .AsNoTracking() + .ToListAsync() + .ConfigureAwait(false); + return PostgreSqlMappers.Map(indicators); + } + + public async Task GetStrategyByNameAsync(string name) + { + var indicator = await _context.Indicators + .AsNoTracking() + .FirstOrDefaultAsync(i => i.Name == name) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(indicator); + } + + public async Task InsertStrategyAsync(Indicator indicator) + { + // Check if indicator already exists for the same user + var existingIndicator = await _context.Indicators + .AsNoTracking() + .FirstOrDefaultAsync(i => i.Name == indicator.Name && + ((indicator.User == null && i.UserName == null) || + (indicator.User != null && i.UserName == indicator.User.Name))); + + if (existingIndicator != null) + { + throw new InvalidOperationException( + $"Indicator with name '{indicator.Name}' already exists for user '{indicator.User?.Name}'"); + } + + var entity = PostgreSqlMappers.Map(indicator); + _context.Indicators.Add(entity); + await _context.SaveChangesAsync(); + } + + public async Task UpdateStrategyAsync(Indicator indicator) + { + var entity = _context.Indicators + .AsTracking() + .FirstOrDefault(i => i.Name == indicator.Name); + + if (entity != null) + { + entity.Type = indicator.Type; + entity.SignalType = indicator.SignalType; + entity.MinimumHistory = indicator.MinimumHistory; + entity.Period = indicator.Period; + entity.FastPeriods = indicator.FastPeriods; + entity.SlowPeriods = indicator.SlowPeriods; + entity.SignalPeriods = indicator.SignalPeriods; + entity.Multiplier = indicator.Multiplier; + entity.SmoothPeriods = indicator.SmoothPeriods; + entity.StochPeriods = indicator.StochPeriods; + entity.CyclePeriods = indicator.CyclePeriods; + entity.UserName = indicator.User?.Name; + entity.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + } + } + + #endregion + + + + #region Position Methods + + public Position GetPositionByIdentifier(string identifier) + { + return GetPositionByIdentifierAsync(identifier).Result; + } + + public async Task GetPositionByIdentifierAsync(string identifier) + { + var position = await _context.Positions + .AsNoTracking() + .Include(p => p.OpenTrade) + .Include(p => p.StopLossTrade) + .Include(p => p.TakeProfit1Trade) + .Include(p => p.TakeProfit2Trade) + .FirstOrDefaultAsync(p => p.Identifier == identifier) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(position); + } + + public IEnumerable GetPositions(PositionInitiator positionInitiator) + { + return GetPositionsAsync(positionInitiator).Result; + } + + public async Task> GetPositionsAsync(PositionInitiator positionInitiator) + { + var positions = await _context.Positions + .AsNoTracking() + .Include(p => p.OpenTrade) + .Include(p => p.StopLossTrade) + .Include(p => p.TakeProfit1Trade) + .Include(p => p.TakeProfit2Trade) + .Where(p => p.Initiator == positionInitiator) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(positions); + } + + public IEnumerable GetPositionsByStatus(PositionStatus positionStatus) + { + return GetPositionsByStatusAsync(positionStatus).Result; + } + + public async Task> GetPositionsByStatusAsync(PositionStatus positionStatus) + { + var positions = await _context.Positions + .AsNoTracking() + .Include(p => p.OpenTrade) + .Include(p => p.StopLossTrade) + .Include(p => p.TakeProfit1Trade) + .Include(p => p.TakeProfit2Trade) + .Where(p => p.Status == positionStatus) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(positions); + } + + public async Task InsertPositionAsync(Position position) + { + // Check if position already exists for the same user + var existingPosition = await _context.Positions + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Identifier == position.Identifier && + ((position.User == null && p.UserName == null) || + (position.User != null && p.UserName == position.User.Name))); + + if (existingPosition != null) + { + throw new InvalidOperationException( + $"Position with identifier '{position.Identifier}' already exists for user '{position.User?.Name}'"); + } + + var entity = PostgreSqlMappers.Map(position); + + // Handle related trades + if (position.Open != null) + { + var openTrade = PostgreSqlMappers.Map(position.Open); + _context.Trades.Add(openTrade); + await _context.SaveChangesAsync(); + entity.OpenTradeId = openTrade.Id; + } + + if (position.StopLoss != null) + { + var stopLossTrade = PostgreSqlMappers.Map(position.StopLoss); + _context.Trades.Add(stopLossTrade); + await _context.SaveChangesAsync(); + entity.StopLossTradeId = stopLossTrade.Id; + } + + if (position.TakeProfit1 != null) + { + var takeProfit1Trade = PostgreSqlMappers.Map(position.TakeProfit1); + _context.Trades.Add(takeProfit1Trade); + await _context.SaveChangesAsync(); + entity.TakeProfit1TradeId = takeProfit1Trade.Id; + } + + if (position.TakeProfit2 != null) + { + var takeProfit2Trade = PostgreSqlMappers.Map(position.TakeProfit2); + _context.Trades.Add(takeProfit2Trade); + await _context.SaveChangesAsync(); + entity.TakeProfit2TradeId = takeProfit2Trade.Id; + } + + _context.Positions.Add(entity); + await _context.SaveChangesAsync(); + } + + public async Task UpdatePositionAsync(Position position) + { + var entity = _context.Positions + .AsTracking() + .FirstOrDefault(p => p.Identifier == position.Identifier); + + if (entity != null) + { + entity.Date = position.Date; + entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0; + entity.Status = position.Status; + entity.SignalIdentifier = position.SignalIdentifier; + entity.MoneyManagementJson = position.MoneyManagement != null + ? JsonConvert.SerializeObject(position.MoneyManagement) + : null; + entity.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + } + } + + #endregion + + #region Signal Methods + + public IEnumerable GetSignalsByUser(User user) + { + return GetSignalsByUserAsync(user).Result; + } + + public async Task> GetSignalsByUserAsync(User user) + { + var signals = await _context.Signals + .AsNoTracking() + .Where(s => (user == null && s.UserName == null) || + (user != null && s.UserName == user.Name)) + .ToListAsync() + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(signals); + } + + public Signal GetSignalByIdentifier(string identifier, User user = null) + { + return GetSignalByIdentifierAsync(identifier, user).Result; + } + + public async Task GetSignalByIdentifierAsync(string identifier, User user = null) + { + var signal = await _context.Signals + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Identifier == identifier && + ((user == null && s.UserName == null) || + (user != null && s.UserName == user.Name))) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(signal); + } + + public async Task InsertSignalAsync(Signal signal) + { + // Check if signal already exists with the same identifier, date, and user + var existingSignal = _context.Signals + .AsNoTracking() + .FirstOrDefault(s => s.Identifier == signal.Identifier && + s.Date == signal.Date && + ((s.UserName == null && signal.User == null) || + (s.UserName != null && signal.User != null && s.UserName == signal.User.Name))); + + if (existingSignal != null) + { + throw new InvalidOperationException( + $"Signal with identifier '{signal.Identifier}' and date '{signal.Date}' already exists for this user"); + } + + var entity = PostgreSqlMappers.Map(signal); + _context.Signals.Add(entity); + await _context.SaveChangesAsync(); + } + + #endregion + + +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs new file mode 100644 index 0000000..204a3c6 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs @@ -0,0 +1,116 @@ +using System.Data; +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Users; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlUserRepository : IUserRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlUserRepository(ManagingDbContext context) + { + _context = context; + } + + /// + /// Ensures the database connection is open before executing queries + /// + private async Task EnsureConnectionOpenAsync() + { + if (_context.Database.GetDbConnection().State != ConnectionState.Open) + { + await _context.Database.OpenConnectionAsync(); + } + } + + /// + /// Safely closes the database connection if it was opened by us + /// + private async Task SafeCloseConnectionAsync() + { + if (_context.Database.GetDbConnection().State == ConnectionState.Open) + { + await _context.Database.CloseConnectionAsync(); + } + } + + public async Task GetUserByAgentNameAsync(string agentName) + { + try + { + await EnsureConnectionOpenAsync(); + + var userEntity = await _context.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.AgentName == agentName) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(userEntity); + } + catch (Exception) + { + // If there's an error, try to reset the connection + await SafeCloseConnectionAsync(); + throw; + } + } + + public async Task GetUserByNameAsync(string name) + { + try + { + await EnsureConnectionOpenAsync(); + + var userEntity = await _context.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Name == name) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(userEntity); + } + catch (Exception) + { + // If there's an error, try to reset the connection + await SafeCloseConnectionAsync(); + throw; + } + } + + public async Task InsertUserAsync(User user) + { + var userEntity = PostgreSqlMappers.Map(user); + + _context.Users.Add(userEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task UpdateUser(User user) + { + try + { + var userEntity = await _context.Users + .AsTracking() // Explicitly enable tracking for update operations + .FirstOrDefaultAsync(u => u.Name == user.Name) + .ConfigureAwait(false); + + if (userEntity == null) + { + throw new InvalidOperationException($"User with name '{user.Name}' not found"); + } + + userEntity.AgentName = user.AgentName; + userEntity.AvatarUrl = user.AvatarUrl; + userEntity.TelegramChannel = user.TelegramChannel; + + _context.Users.Update(userEntity); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + catch (Exception e) + { + Console.WriteLine(e); + throw new Exception("Cannot update user"); + } + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlWorkerRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlWorkerRepository.cs new file mode 100644 index 0000000..07fefe2 --- /dev/null +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlWorkerRepository.cs @@ -0,0 +1,66 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Common; +using Managing.Domain.Workers; +using Managing.Infrastructure.Databases.PostgreSql.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Managing.Infrastructure.Databases.PostgreSql; + +public class PostgreSqlWorkerRepository : IWorkerRepository +{ + private readonly ManagingDbContext _context; + + public PostgreSqlWorkerRepository(ManagingDbContext context) + { + _context = context; + } + + public async Task DisableWorker(Enums.WorkerType workerType) + { + var entity = await GetWorkerEntity(workerType); + if (entity == null) throw new InvalidOperationException($"Worker with type '{workerType}' not found"); + entity.IsActive = false; + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task EnableWorker(Enums.WorkerType workerType) + { + var entity = await GetWorkerEntity(workerType); + if (entity == null) throw new InvalidOperationException($"Worker with type '{workerType}' not found"); + entity.IsActive = true; + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task GetWorkerAsync(Enums.WorkerType workerType) + { + var entity = await GetWorkerEntity(workerType); + return PostgreSqlMappers.Map(entity); + } + + public async Task> GetWorkers() + { + var entities = await _context.Workers.AsNoTracking().ToListAsync().ConfigureAwait(false); + return PostgreSqlMappers.Map(entities); + } + + public async Task InsertWorker(Worker worker) + { + var entity = PostgreSqlMappers.Map(worker); + await _context.Workers.AddAsync(entity).ConfigureAwait(false); + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + public async Task UpdateWorker(Enums.WorkerType workerType, int executionCount) + { + var entity = await GetWorkerEntity(workerType); + if (entity == null) throw new InvalidOperationException($"Worker with type '{workerType}' not found"); + entity.ExecutionCount = executionCount; + entity.LastRunTime = DateTime.UtcNow; + await _context.SaveChangesAsync().ConfigureAwait(false); + } + + private async Task GetWorkerEntity(Enums.WorkerType workerType) + { + return await _context.Workers.FirstOrDefaultAsync(w => w.WorkerType == workerType).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/SettingsRepository.cs b/src/Managing.Infrastructure.Database/SettingsRepository.cs deleted file mode 100644 index dc9c29e..0000000 --- a/src/Managing.Infrastructure.Database/SettingsRepository.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Users; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class SettingsRepository : ISettingsRepository -{ - private readonly IMongoRepository _moneyManagementRepository; - - public SettingsRepository(IMongoRepository moneyManagementRepository) - { - _moneyManagementRepository = moneyManagementRepository; - } - - public void DeleteMoneyManagement(string name) - { - var moneyManagement = _moneyManagementRepository.FindOne(m => m.Name == name); - _moneyManagementRepository.DeleteById(moneyManagement.Id.ToString()); - } - - public void DeleteMoneyManagements() - { - _moneyManagementRepository.DropCollection(); - } - - public async Task GetMoneyManagement(string name) - { - var moneyManagement = await _moneyManagementRepository.FindOneAsync(m => m.Name == name); - return MongoMappers.Map(moneyManagement); - } - - public IEnumerable GetMoneyManagements() - { - var moneyManagements = _moneyManagementRepository.FindAll(); - return moneyManagements.Select(m => MongoMappers.Map(m)); - } - - public async Task InsertMoneyManagement(MoneyManagement request) - { - await _moneyManagementRepository.InsertOneAsync(MongoMappers.Map(request)); - } - - public void UpdateMoneyManagement(MoneyManagement moneyManagement) - { - var mm = _moneyManagementRepository.FindOne(m => m.Name == moneyManagement.Name); - var dto = MongoMappers.Map(moneyManagement); - dto.Id = mm.Id; - _moneyManagementRepository.Update(dto); - } - - // User-specific implementations - public async Task GetMoneyManagementByUser(User user, string name) - { - var moneyManagement = await _moneyManagementRepository.FindOneAsync(m => - m.Name == name && - m.User != null && - m.User.Name == user.Name); - - return MongoMappers.Map(moneyManagement); - } - - public IEnumerable GetMoneyManagementsByUser(User user) - { - var moneyManagements = _moneyManagementRepository.AsQueryable() - .Where(m => m.User != null && m.User.Name == user.Name) - .ToList(); - - return moneyManagements.Select(m => MongoMappers.Map(m)); - } - - public void DeleteMoneyManagementByUser(User user, string name) - { - var moneyManagement = _moneyManagementRepository.FindOne(m => - m.Name == name && - m.User != null && - m.User.Name == user.Name); - - if (moneyManagement != null) - { - _moneyManagementRepository.DeleteById(moneyManagement.Id.ToString()); - } - } - - public void DeleteMoneyManagementsByUser(User user) - { - var moneyManagements = _moneyManagementRepository.AsQueryable() - .Where(m => m.User != null && m.User.Name == user.Name) - .ToList(); - - foreach (var moneyManagement in moneyManagements) - { - _moneyManagementRepository.DeleteById(moneyManagement.Id.ToString()); - } - } -} diff --git a/src/Managing.Infrastructure.Database/StatisticRepository.cs b/src/Managing.Infrastructure.Database/StatisticRepository.cs deleted file mode 100644 index d76f4ea..0000000 --- a/src/Managing.Infrastructure.Database/StatisticRepository.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Statistics; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class StatisticRepository : IStatisticRepository -{ - private readonly IMongoRepository _bestTrader; - private readonly IMongoRepository _badTrader; - private readonly IMongoRepository _topRepository; - private readonly IMongoRepository _spotlightRepository; - private readonly IMongoRepository _fundingRateRepository; - - public StatisticRepository( - IMongoRepository topRepository, - IMongoRepository spotlightRepository, - IMongoRepository bestTrader, - IMongoRepository badTrader, - IMongoRepository fundingRateRepository) - { - _topRepository = topRepository; - _spotlightRepository = spotlightRepository; - _bestTrader = bestTrader; - _badTrader = badTrader; - _fundingRateRepository = fundingRateRepository; - } - - public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker) - { - await _topRepository.InsertOneAsync(MongoMappers.Map(topVolumeTicker)); - } - - public IList GetTopVolumeTickers(DateTime date) - { - var top = _topRepository.FilterBy(t => date < t.Date); - return MongoMappers.Map(top); - } - - public async Task SaveSpotligthtOverview(SpotlightOverview overview) - { - await _spotlightRepository.InsertOneAsync(MongoMappers.Map(overview)); - } - - public IList GetSpotlightOverviews(DateTime date) - { - var overviews = _spotlightRepository.FilterBy(t => date < t.DateTime); - return MongoMappers.Map(overviews); - } - - public void UpdateSpotlightOverview(SpotlightOverview overview) - { - var s = _spotlightRepository.FindOne(o => o.Identifier == overview.Identifier); - var dto = MongoMappers.Map(overview); - dto.Id = s.Id; - _spotlightRepository.Update(dto); - } - - public List GetBestTraders() - { - return MongoMappers.Map(_bestTrader.FindAll()); - } - - public void UpdateBestTrader(Trader trader) - { - var t = _bestTrader.FindOne(t => t.Address == trader.Address); - var dto = MongoMappers.Map(trader); - dto.Id = t.Id; - _bestTrader.Update(dto); - } - - public async Task InsertBestTrader(Trader trader) - { - await _bestTrader.InsertOneAsync(MongoMappers.Map(trader)); - } - - public async Task RemoveBestTrader(Trader trader) - { - await _bestTrader.DeleteOneAsync(t => t.Address == trader.Address); - } - - public List GetBadTraders() - { - return MongoMappers.Map(_badTrader.FindAll()); - } - - public void UpdateBadTrader(Trader trader) - { - var t = _badTrader.FindOne(t => t.Address == trader.Address); - var dto = MongoMappers.BadTraderMap(trader); - dto.Id = t.Id; - _badTrader.Update(dto); - } - - public async Task InsertBadTrader(Trader trader) - { - await _badTrader.InsertOneAsync(MongoMappers.BadTraderMap(trader)); - } - - public async Task RemoveBadTrader(Trader trader) - { - await _badTrader.DeleteOneAsync(t => t.Address == trader.Address); - } - - public List GetFundingRates() - { - return _fundingRateRepository.FindAll().Select(MongoMappers.Map).ToList(); - } - - public async Task RemoveFundingRate(FundingRate oldRate) - { - var rate = _fundingRateRepository.FindOne(r => r.Ticker == oldRate.Ticker && r.Exchange == oldRate.Exchange); - await _fundingRateRepository.DeleteOneAsync(r => r.Id == rate.Id); - } - - public async Task InsertFundingRate(FundingRate newRate) - { - await _fundingRateRepository.InsertOneAsync(MongoMappers.Map(newRate)); - } - - public void UpdateFundingRate(FundingRate oldRate, FundingRate newRate) - { - var f = _fundingRateRepository.FindOne(t => t.Ticker == oldRate.Ticker && t.Exchange == oldRate.Exchange); - var dto = MongoMappers.Map(newRate); - dto.Id = f.Id; - _fundingRateRepository.Update(dto); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/SynthRepository.cs b/src/Managing.Infrastructure.Database/SynthRepository.cs deleted file mode 100644 index aa965f3..0000000 --- a/src/Managing.Infrastructure.Database/SynthRepository.cs +++ /dev/null @@ -1,223 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Synth.Models; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; -using Microsoft.Extensions.Logging; - -namespace Managing.Infrastructure.Databases; - -/// -/// Repository implementation for Synth-related data operations using MongoDB -/// Provides persistence for leaderboard and individual predictions data -/// -public class SynthRepository : ISynthRepository -{ - private readonly IMongoRepository _leaderboardRepository; - private readonly IMongoRepository _individualPredictionsRepository; - private readonly ILogger _logger; - - public SynthRepository( - IMongoRepository leaderboardRepository, - IMongoRepository individualPredictionsRepository, - ILogger logger) - { - _leaderboardRepository = leaderboardRepository; - _individualPredictionsRepository = individualPredictionsRepository; - _logger = logger; - } - - /// - /// Gets cached leaderboard data by cache key - /// - public async Task GetLeaderboardAsync(string cacheKey) - { - try - { - var dto = await _leaderboardRepository.FindOneAsync(x => x.CacheKey == cacheKey); - - if (dto == null) - { - _logger.LogDebug($"🔍 **Synth Cache** - No leaderboard cache found for key: {cacheKey}"); - return null; - } - - var result = MongoMappers.Map(dto); - _logger.LogDebug($"📦 **Synth Cache** - Retrieved leaderboard from MongoDB for key: {cacheKey}"); - - return result; - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error retrieving leaderboard cache for key: {cacheKey}"); - return null; - } - } - - /// - /// Saves leaderboard data to MongoDB - /// - public async Task SaveLeaderboardAsync(SynthMinersLeaderboard leaderboard) - { - try - { - leaderboard.CreatedAt = DateTime.UtcNow; - var dto = MongoMappers.Map(leaderboard); - - // Check if we already have this cache key and update instead of inserting - var existing = await _leaderboardRepository.FindOneAsync(x => x.CacheKey == dto.CacheKey); - if (existing != null) - { - dto.Id = existing.Id; - _leaderboardRepository.Update(dto); - _logger.LogDebug($"💾 **Synth Cache** - Updated leaderboard in MongoDB for key: {dto.CacheKey}"); - } - else - { - await _leaderboardRepository.InsertOneAsync(dto); - _logger.LogDebug($"💾 **Synth Cache** - Saved new leaderboard to MongoDB for key: {dto.CacheKey}"); - } - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error saving leaderboard cache for key: {leaderboard.GetCacheKey()}"); - } - } - - /// - /// Gets individual cached prediction data by asset, parameters, and miner UIDs - /// - public async Task> GetIndividualPredictionsAsync( - string asset, - int timeIncrement, - int timeLength, - List minerUids, - bool isBacktest, - DateTime? signalDate) - { - try - { - var results = new List(); - - foreach (var minerUid in minerUids) - { - // Build cache key for individual prediction - var cacheKey = $"{asset}_{timeIncrement}_{timeLength}_{minerUid}"; - if (isBacktest && signalDate.HasValue) - { - cacheKey += $"_backtest_{signalDate.Value:yyyy-MM-dd-HH}"; - } - - var dto = await _individualPredictionsRepository.FindOneAsync(x => x.CacheKey == cacheKey); - - if (dto != null) - { - var prediction = MongoMappers.Map(dto); - if (prediction != null) - { - results.Add(prediction); - } - } - } - - if (results.Any()) - { - _logger.LogDebug($"📦 **Synth Individual Cache** - Retrieved {results.Count}/{minerUids.Count} individual predictions for {asset}"); - } - else - { - _logger.LogDebug($"🔍 **Synth Individual Cache** - No individual predictions found for {asset}"); - } - - return results; - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error retrieving individual predictions cache for asset: {asset}"); - return new List(); - } - } - - /// - /// Saves individual prediction data to MongoDB - /// - public async Task SaveIndividualPredictionAsync(SynthPrediction prediction) - { - try - { - prediction.CreatedAt = DateTime.UtcNow; - var dto = MongoMappers.Map(prediction); - - // Check if we already have this cache key and update instead of inserting - var existing = await _individualPredictionsRepository.FindOneAsync(x => x.CacheKey == dto.CacheKey); - if (existing != null) - { - dto.Id = existing.Id; - _individualPredictionsRepository.Update(dto); - _logger.LogDebug($"💾 **Synth Individual Cache** - Updated individual prediction for miner {prediction.MinerUid}"); - } - else - { - await _individualPredictionsRepository.InsertOneAsync(dto); - _logger.LogDebug($"💾 **Synth Individual Cache** - Saved new individual prediction for miner {prediction.MinerUid}"); - } - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error saving individual prediction cache for miner {prediction.MinerUid}: {ex.Message}"); - } - } - - /// - /// Saves multiple individual predictions to MongoDB in batch - /// - public async Task SaveIndividualPredictionsAsync(List predictions) - { - if (!predictions.Any()) - { - return; - } - - try - { - var saveTasks = new List(); - - foreach (var prediction in predictions) - { - // Save each prediction individually to handle potential conflicts - saveTasks.Add(SaveIndividualPredictionAsync(prediction)); - } - - await Task.WhenAll(saveTasks); - - _logger.LogInformation($"💾 **Synth Individual Cache** - Successfully saved {predictions.Count} individual predictions to MongoDB"); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error saving batch of {predictions.Count} individual predictions"); - } - } - - /// - /// Cleans up old cached data beyond the retention period - /// - public async Task CleanupOldDataAsync(int retentionDays = 30) - { - try - { - var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays); - - // Clean up old leaderboard data - await _leaderboardRepository.DeleteManyAsync(x => x.CreatedAt < cutoffDate); - - // Clean up old individual predictions data - await _individualPredictionsRepository.DeleteManyAsync(x => x.CreatedAt < cutoffDate); - - _logger.LogInformation($"🧹 **Synth Cache** - Cleaned up old Synth cache data older than {retentionDays} days"); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error during cleanup of old Synth cache data"); - } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/TradingRepository.cs b/src/Managing.Infrastructure.Database/TradingRepository.cs deleted file mode 100644 index ebbce76..0000000 --- a/src/Managing.Infrastructure.Database/TradingRepository.cs +++ /dev/null @@ -1,272 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Scenarios; -using Managing.Domain.Strategies; -using Managing.Domain.Trades; -using Managing.Domain.Users; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; -using MongoDB.Driver; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Databases; - -public class TradingRepository : ITradingRepository -{ - private readonly IMongoRepository _scenarioRepository; - private readonly IMongoRepository _signalRepository; - private readonly IMongoRepository _positionRepository; - private readonly IMongoRepository _indicatorRepository; - private readonly IMongoRepository _feeRepository; - - public TradingRepository( - IMongoRepository scenarioRepository, - IMongoRepository signalRepository, - IMongoRepository positionRepository, - IMongoRepository indicatorRepository, - IMongoRepository feeRepository) - { - _scenarioRepository = scenarioRepository; - _signalRepository = signalRepository; - _positionRepository = positionRepository; - _indicatorRepository = indicatorRepository; - _feeRepository = feeRepository; - } - - public void DeleteScenario(string name) - { - var scenario = _scenarioRepository.FindOne(s => s.Name == name); - _scenarioRepository.DeleteById(scenario.Id.ToString()); - } - - public void DeleteScenarios() - { - _scenarioRepository.DropCollection(); - } - - public void DeleteIndicators() - { - _indicatorRepository.DropCollection(); - } - - public void DeleteIndicator(string name) - { - var strategy = _indicatorRepository.FindOne(s => s.Name == name); - _indicatorRepository.DeleteById(strategy.Id.ToString()); - } - - public Position GetPositionByIdentifier(string identifier) - { - var position = _positionRepository.FindOne(p => p.Identifier == identifier); - return MongoMappers.Map(position); - } - - public IEnumerable GetPositions(PositionInitiator positionInitiator) - { - var filter = Builders.Filter.Eq(p => p.Initiator, positionInitiator); - var positions = _positionRepository.FilterBy(filter); - return positions.Select(MongoMappers.Map); - } - - public IEnumerable GetPositionsByStatus(PositionStatus positionStatus) - { - var filter = Builders.Filter.Eq(p => p.Status, positionStatus); - var positions = _positionRepository.FilterBy(filter); - return positions.Select(MongoMappers.Map); - } - - public Scenario GetScenarioByName(string name) - { - var scenario = _scenarioRepository.FindOne(s => s.Name == name); - return MongoMappers.Map(scenario); - } - - public IEnumerable GetScenarios() - { - var scenarios = _scenarioRepository.FindAll(); - return scenarios.Select(s => MongoMappers.Map(s)); - } - - public IEnumerable GetIndicators() - { - var indicators = _indicatorRepository.FindAll(); - return indicators.Select(MongoMappers.Map); - } - - public Indicator GetStrategyByName(string name) - { - var strategy = _indicatorRepository.FindOne(s => s.Name == name); - return MongoMappers.Map(strategy); - } - - public void InsertPosition(Position position) - { - // Check if position already exists for the same user - var existingPosition = _positionRepository.FindOne(p => - p.Identifier == position.Identifier && - (position.User == null || (p.User != null && p.User.Name == position.User.Name))); - - if (existingPosition != null) - { - throw new InvalidOperationException( - $"Position with identifier '{position.Identifier}' already exists for user '{position.User?.Name}'"); - } - - var dto = MongoMappers.Map(position); - _positionRepository.InsertOne(dto); - } - - public void InsertScenario(Scenario scenario) - { - // Check if scenario already exists for the same user - var existingScenario = _scenarioRepository.FindOne(s => - s.Name == scenario.Name && - (scenario.User == null || (s.User != null && s.User.Name == scenario.User.Name))); - - if (existingScenario != null) - { - throw new InvalidOperationException( - $"Scenario with name '{scenario.Name}' already exists for user '{scenario.User?.Name}'"); - } - - var strategyDtos = new List(); - foreach (var strategy in scenario.Indicators) - { - var dto = _indicatorRepository.FindOne(s => s.Name == strategy.Name); - strategyDtos.Add(dto); - } - - var scenarioDto = new ScenarioDto - { - Name = scenario.Name, - Indicators = strategyDtos, - User = scenario.User != null ? MongoMappers.Map(scenario.User) : null - }; - - _scenarioRepository.InsertOne(scenarioDto); - } - - public void InsertSignal(Signal signal) - { - // Check if signal already exists with the same identifier, date, and user - var existingSignal = _signalRepository.FindOne(s => - s.Identifier == signal.Identifier && - s.Date == signal.Date && - ((s.User == null && signal.User == null) || - (s.User != null && signal.User != null && s.User.Name == signal.User.Name))); - - if (existingSignal != null) - { - throw new InvalidOperationException( - $"Signal with identifier '{signal.Identifier}' and date '{signal.Date}' already exists for this user"); - } - - var dto = MongoMappers.Map(signal); - _signalRepository.InsertOne(dto); - } - - public void InsertStrategy(Indicator indicator) - { - // Check if strategy already exists for the same user - var existingStrategy = _indicatorRepository.FindOne(s => - s.Name == indicator.Name && - (indicator.User == null || (s.User != null && s.User.Name == indicator.User.Name))); - - if (existingStrategy != null) - { - throw new InvalidOperationException( - $"Strategy with name '{indicator.Name}' already exists for user '{indicator.User?.Name}'"); - } - - var dto = MongoMappers.Map(indicator); - _indicatorRepository.InsertOne(dto); - } - - public void InsertFee(Fee fee) - { - // Check if fee for this exchange already exists (fee is global, not user-specific) - var existingFee = _feeRepository.FindOne(f => f.Exchange == fee.Exchange); - if (existingFee != null) - { - throw new InvalidOperationException($"Fee for exchange '{fee.Exchange}' already exists"); - } - - var dto = MongoMappers.Map(fee); - _feeRepository.InsertOne(dto); - } - - public void UpdatePosition(Position position) - { - var p = _positionRepository.FindOne(p => p.Identifier.Equals(position.Identifier)); - var dto = MongoMappers.Map(position); - dto.Id = p.Id; - _positionRepository.Update(dto); - } - - public Fee GetFee(TradingExchanges exchange) - { - var fee = _feeRepository.FindOne(f => f.Exchange == exchange); - return MongoMappers.Map(fee); - } - - public IEnumerable GetSignalsByUser(User user) - { - IEnumerable signals; - - if (user == null) - { - signals = _signalRepository.FilterBy(s => s.User == null); - } - else - { - signals = _signalRepository.FilterBy(s => s.User != null && s.User.Name == user.Name); - } - - return signals.Select(MongoMappers.Map); - } - - public Signal GetSignalByIdentifier(string identifier, User user = null) - { - SignalDto signal; - - if (user == null) - { - signal = _signalRepository.FindOne(s => - s.Identifier == identifier && - s.User == null); - } - else - { - signal = _signalRepository.FindOne(s => - s.Identifier == identifier && - s.User != null && - s.User.Name == user.Name); - } - - return MongoMappers.Map(signal); - } - - public void UpdateFee(Fee fee) - { - var f = _feeRepository.FindOne(f => f.Exchange == fee.Exchange); - var dto = MongoMappers.Map(fee); - dto.Id = f.Id; - _feeRepository.Update(dto); - } - - public void UpdateScenario(Scenario scenario) - { - var s = _scenarioRepository.FindOne(s => s.Name == scenario.Name); - var dto = MongoMappers.Map(scenario); - dto.Id = s.Id; - _scenarioRepository.Update(dto); - } - - public void UpdateStrategy(Indicator indicator) - { - var s = _indicatorRepository.FindOne(s => s.Name == indicator.Name); - var dto = MongoMappers.Map(indicator); - dto.Id = s.Id; - _indicatorRepository.Update(dto); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/UserRepository.cs b/src/Managing.Infrastructure.Database/UserRepository.cs deleted file mode 100644 index 2fa8354..0000000 --- a/src/Managing.Infrastructure.Database/UserRepository.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Users; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class UserRepository : IUserRepository -{ - private readonly IMongoRepository _userRepository; - - public UserRepository(IMongoRepository userRepository) - { - _userRepository = userRepository; - } - - public async Task GetUserByAgentNameAsync(string agentName) - { - var user = await _userRepository.FindOneAsync(u => u.AgentName == agentName); - return MongoMappers.Map(user); - } - - public async Task GetUserByNameAsync(string name) - { - var user = await _userRepository.FindOneAsync(u => u.Name == name); - return MongoMappers.Map(user); - } - - public async Task InsertUserAsync(User user) - { - await _userRepository.InsertOneAsync(MongoMappers.Map(user)); - } - - public async Task UpdateUser(User user) - { - try - { - var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name); - dto.AgentName = user.AgentName; - dto.AvatarUrl = user.AvatarUrl; - dto.TelegramChannel = user.TelegramChannel; - _userRepository.Update(dto); - } - catch (Exception e) - { - Console.WriteLine(e); - throw new Exception("Cannot update user"); - } - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/WorkerRepository.cs b/src/Managing.Infrastructure.Database/WorkerRepository.cs deleted file mode 100644 index f684ee9..0000000 --- a/src/Managing.Infrastructure.Database/WorkerRepository.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Common; -using Managing.Domain.Workers; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class WorkerRepository : IWorkerRepository -{ - private readonly IMongoRepository _workerRepository; - public WorkerRepository(IMongoRepository workerRepository) - { - _workerRepository = workerRepository; - } - - public async Task DisableWorker(Enums.WorkerType workerType) - { - var worker = await GetWorker(workerType); - worker.IsActive = false; - _workerRepository.Update(worker); - } - - public async Task EnableWorker(Enums.WorkerType workerType) - { - var worker = await GetWorker(workerType); - worker.IsActive = true; - _workerRepository.Update(worker); - } - - public async Task GetWorkerAsync(Enums.WorkerType workerType) - { - return MongoMappers.Map(await GetWorker(workerType)); - } - - public async Task InsertWorker(Worker worker) - { - await _workerRepository.InsertOneAsync(MongoMappers.Map(worker)); - } - - public async Task UpdateWorker(Enums.WorkerType workerType, int executionCount) - { - var worker = await GetWorker(workerType); - worker.ExecutionCount = executionCount; - worker.LastRunTime = DateTime.UtcNow; - _workerRepository.Update(worker); - } - - public IEnumerable GetWorkers() - { - var workers = _workerRepository.FindAll(); - return workers.Select(MongoMappers.Map); - } - - private async Task GetWorker(Enums.WorkerType workerType) - { - return await _workerRepository.FindOneAsync(w => w.WorkerType == workerType); - } -} diff --git a/src/Managing.Infrastructure.Database/WorkflowRepository.cs b/src/Managing.Infrastructure.Database/WorkflowRepository.cs deleted file mode 100644 index e6faaf4..0000000 --- a/src/Managing.Infrastructure.Database/WorkflowRepository.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Domain.Workflows.Synthetics; -using Managing.Infrastructure.Databases.MongoDb; -using Managing.Infrastructure.Databases.MongoDb.Abstractions; -using Managing.Infrastructure.Databases.MongoDb.Collections; - -namespace Managing.Infrastructure.Databases; - -public class WorkflowRepository : IWorkflowRepository -{ - private readonly IMongoRepository _workflowRepository; - - public WorkflowRepository(IMongoRepository workflowRepository) - { - _workflowRepository = workflowRepository; - } - public bool DeleteWorkflow(string name) - { - var workflow = _workflowRepository.FindOne(m => m.Name == name); - _workflowRepository.DeleteById(workflow.Id.ToString()); - return true; - } - - public async Task InsertWorkflow(SyntheticWorkflow workflow) - { - await _workflowRepository.InsertOneAsync(MongoMappers.Map(workflow)); - } - - public async Task UpdateWorkflow(SyntheticWorkflow workflow) - { - var w = await _workflowRepository.FindOneAsync(m => m.Name == workflow.Name); - var dto = MongoMappers.Map(workflow); - dto.Id = w.Id; - _workflowRepository.Update(dto); - } - - public async Task GetWorkflow(string name) - { - var workflow = await _workflowRepository.FindOneAsync(m => m.Name == name); - return MongoMappers.Map(workflow); - } - - public IEnumerable GetWorkflows() - { - var workflows = _workflowRepository.FindAll(); - return workflows.Select(m => MongoMappers.Map(m)); - } -} diff --git a/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs b/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs index 939b3f1..c033f9a 100644 --- a/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs @@ -1,5 +1,4 @@ -using Managing.Common; -using Managing.Domain.Accounts; +using Managing.Domain.Accounts; using Managing.Domain.Candles; using Managing.Domain.Statistics; using Managing.Domain.Trades; @@ -9,7 +8,7 @@ namespace Managing.Infrastructure.Exchanges.Abstractions; public interface IExchangeProcessor { - public Enums.TradingExchanges Exchange(); + public TradingExchanges Exchange(); void LoadClient(Account account); Task OpenTrade( Account account, @@ -27,7 +26,7 @@ public interface IExchangeProcessor decimal? takeProfitPrice = null); Task GetBalance(Account account, bool isForPaperTrading = false); Task> GetBalances(Account account, bool isForPaperTrading = false); - decimal GetPrice(Account account, Ticker ticker, DateTime date); + Task GetPrice(Account account, Ticker ticker, DateTime date); Task GetTrade(Account account, string order, Ticker ticker); Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); decimal GetVolume(Account account, Ticker ticker); diff --git a/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs b/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs index d7f2b7d..3498d4d 100644 --- a/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs +++ b/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs @@ -1,52 +1,12 @@ using Binance.Net.Interfaces; using FTX.Net.Objects.Models; -using Kraken.Net.Objects.Models; using Managing.Domain.Candles; -using Managing.Infrastructure.Databases.MongoDb.Collections; using static Managing.Common.Enums; namespace Managing.Infrastructure.Exchanges { public static class CandleHelpers { - public static CandleDto Convert(IBinanceKline candle, Ticker ticker, Timeframe timeframe) - { - return new CandleDto() - { - Ticker = ticker.ToString(), - Timeframe = timeframe, - BaseVolume = candle.Volume, - Close = candle.ClosePrice, - CloseTime = candle.CloseTime, - Open = candle.OpenPrice, - OpenTime = candle.OpenTime, - High = candle.HighPrice, - Low = candle.LowPrice, - QuoteVolume = candle.QuoteVolume, - TakerBuyBaseVolume = candle.TakerBuyBaseVolume, - TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, - TradeCount = candle.TradeCount, - }; - } - - public static CandleDto Convert(KrakenStreamKline candle, Ticker ticker, Timeframe timeframe) - { - return new CandleDto() - { - Ticker = ticker.ToString(), - Timeframe = timeframe, - BaseVolume = candle.Volume, - Close = candle.ClosePrice, - CloseTime = candle.CloseTime, - Open = candle.OpenPrice, - OpenTime = candle.OpenTime, - High = candle.HighPrice, - Low = candle.LowPrice, - QuoteVolume = candle.VolumeWeightedAveragePrice, - TradeCount = candle.TradeCount - }; - } - internal static Candle Map(FTXKline lastCandle) { return new Candle @@ -65,18 +25,14 @@ namespace Managing.Infrastructure.Exchanges { Ticker = ticker.ToString(), Timeframe = timeframe, - BaseVolume = candle.Volume, + Volume = candle.Volume, Close = candle.ClosePrice, Date = candle.CloseTime, Open = candle.OpenPrice, OpenTime = candle.OpenTime, High = candle.HighPrice, Low = candle.LowPrice, - QuoteVolume = candle.QuoteVolume, - TakerBuyBaseVolume = candle.TakerBuyBaseVolume, - TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, - TradeCount = candle.TradeCount, }; } } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Exchanges/ExchangeService.cs b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs index 6219999..2169d1a 100644 --- a/src/Managing.Infrastructure.Exchanges/ExchangeService.cs +++ b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs @@ -243,10 +243,10 @@ namespace Managing.Infrastructure.Exchanges return processor.GetFee(account); } - public decimal GetPrice(Account account, Ticker ticker, DateTime date) + public async Task GetPrice(Account account, Ticker ticker, DateTime date) { var processor = GetProcessor(account); - return processor.GetPrice(account, ticker, date); + return await processor.GetPrice(account, ticker, date); } public Candle GetCandle(Account account, Ticker ticker, DateTime date) @@ -274,12 +274,12 @@ namespace Managing.Infrastructure.Exchanges return tickers.ToList(); } - public decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, + public async Task GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction) { if (IsEvmExchange(account)) { - return GetPrice(account, ticker, DateTime.UtcNow); + return await GetPrice(account, ticker, DateTime.UtcNow); } return GetOrderbook(account, ticker).GetBestPrice(direction, quantity); diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs index fb4e952..2c9b7d8 100644 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs @@ -1,5 +1,4 @@ -using Managing.Common; -using Managing.Domain.Accounts; +using Managing.Domain.Accounts; using Managing.Domain.Candles; using Managing.Domain.Statistics; using Managing.Domain.Trades; @@ -12,12 +11,12 @@ namespace Managing.Infrastructure.Exchanges.Exchanges { public abstract void LoadClient(Account account); public abstract Task CancelOrder(Account account, Ticker ticker); - public abstract Enums.TradingExchanges Exchange(); + public abstract TradingExchanges Exchange(); public abstract Task GetBalance(Account account, bool isForPaperTrading = false); public abstract Candle GetCandle(Account account, Ticker ticker, DateTime date); public abstract Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); public abstract decimal GetFee(Account account, bool isForPaperTrading = false); - public abstract decimal GetPrice(Account account, Ticker ticker, DateTime date); + public abstract Task GetPrice(Account account, Ticker ticker, DateTime date); public abstract Task GetQuantityInPosition(Account account, Ticker ticker); public abstract Task GetTrade(Account account, string order, Ticker ticker); public abstract Task> GetTrades(Account account, Ticker ticker); diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs deleted file mode 100644 index 823dc14..0000000 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs +++ /dev/null @@ -1,198 +0,0 @@ -using Binance.Net.Clients; -using Binance.Net.Enums; -using Binance.Net.Interfaces.Clients; -using CryptoExchange.Net.Authentication; -using Managing.Common; -using Managing.Core; -using Managing.Domain.Accounts; -using Managing.Domain.Candles; -using Managing.Domain.Statistics; -using Managing.Domain.Trades; -using Managing.Infrastructure.Exchanges.Helpers; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Exchanges.Exchanges; - -public class BinanceProcessor : BaseProcessor -{ - private ILogger _logger; - private IBinanceRestClient _binanceClient; - - public BinanceProcessor(ILogger logger) - { - _logger = logger; - } - - public override async Task CancelOrder(Account account, Ticker ticker) - { - var binanceResult = - await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); - return binanceResult.Success; - } - - public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Binance; - - public override async Task GetBalance(Account account, bool isForPaperTrading = false) - { - var balance = 0m; - var binanceBalance = await _binanceClient.UsdFuturesApi.Account.GetBalancesAsync(); - foreach (var item in binanceBalance.Data) - { - balance += item.AvailableBalance; - } - - return balance; - } - - public override Task> GetBalances(Account account, bool isForPaperTrading = false) - { - throw new NotImplementedException(); - } - - public override Candle GetCandle(Account account, Ticker ticker, DateTime date) - { - throw new NotImplementedException(); - } - - public override async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, - Timeframe timeframe) - { - var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync( - BinanceHelpers.ToBinanceTicker(ticker), - BinanceHelpers.Map(timeframe), startDate); - - return (List)binanceCandles.Data.Select(binanceKline => - BinanceHelpers.Map(binanceKline, ticker, account.Exchange)); - } - - public override decimal GetFee(Account account, bool isForPaperTrading = false) - { - throw new NotImplementedException(); - } - - public override Orderbook GetOrderbook(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task> GetOrders(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override decimal GetPrice(Account account, Ticker ticker, DateTime date) - { - var binancePrice = _binanceClient.UsdFuturesApi.ExchangeData - .GetPriceAsync(BinanceHelpers.ToBinanceTicker(ticker)).Result.Data; - return binancePrice.Price; - } - - public override async Task GetQuantityInPosition(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override async Task GetTrade(Account account, string order, Ticker ticker) - { - var binanceOrder = - await _binanceClient.UsdFuturesApi.Trading.GetOrderAsync(BinanceHelpers.ToBinanceTicker(ticker), - origClientOrderId: order); - return BinanceHelpers.Map(binanceOrder.Data); - } - - public override Task GetTrade(string reference, string orderId, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task> GetFundingRates() - { - throw new NotImplementedException(); - } - - public override Task> GetPositions(Account account) - { - throw new NotImplementedException(); - } - - public override async Task> GetTrades(Account account, Ticker ticker) - { - var binanceOrder = - await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); - return (List)binanceOrder.Data.Select(o => BinanceHelpers.Map(o)); - } - - public override decimal GetVolume(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override void LoadClient(Account account) - { - var credentials = new ApiCredentials(account.Key, account.Secret); - _binanceClient = new BinanceRestClient((options) => { options.ApiCredentials = credentials; }); - } - - public override async Task OpenTrade(Account account, Ticker ticker, TradeDirection direction, decimal price, - decimal quantity, decimal? leverage = null, TradeType tradeType = TradeType.Limit, bool reduceOnly = false, - bool isForPaperTrading = false, DateTime? currentDate = null, bool ioc = true, - decimal? stopLossPrice = null, - decimal? takeProfitPrice = null) - { - var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, - leverage, "", ""); - trade.SetQuantity(quantity, GetQuantityPrecision(account, ticker)); - trade.SetPrice(price, GetPricePrecision(account, ticker)); - - if (trade.Quantity <= 0) - throw new InvalidOperationException( - $"Minimum quantity not match, cannot execute trade {direction} for {ticker}."); - - var binanceOrderType = BinanceHelpers.BinanceOrderTypeMap(tradeType); - var binanceResult = await _binanceClient.UsdFuturesApi.Trading.PlaceOrderAsync( - BinanceHelpers.ToBinanceTicker(ticker), - direction != TradeDirection.Long ? OrderSide.Sell : OrderSide.Buy, - binanceOrderType, - price: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null, - quantity: trade.Quantity, - reduceOnly: reduceOnly, - priceProtect: true, - timeInForce: binanceOrderType == FuturesOrderType.Limit ? TimeInForce.GoodTillCanceled : null, - activationPrice: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null, - stopPrice: binanceOrderType == FuturesOrderType.StopMarket ? trade.Price : null); - - _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(binanceResult)); - - var binanceOrderExecuted = BinanceHelpers.Map(binanceResult, leverage); - - if (binanceResult.Success) - { - var binanceOrder = await GetTrade(account, "", ticker); - - trade.Price = binanceOrder.Price; - trade.SetStatus(binanceOrder.Status); - trade.SetExchangeOrderId(""); - trade.SetMessage(""); - } - - return trade; - } - - private int GetPricePrecision(Account account, Ticker ticker) - { - var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data; - var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker)) - .PriceFilter.TickSize; - return MathHelpers.GetDecimalPlaces(precision); - } - - private int GetQuantityPrecision(Account account, Ticker ticker) - { - var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data; - var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker)) - .QuantityPrecision; - return Convert.ToInt32(precision); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs index 96f13e8..b961268 100644 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs @@ -83,9 +83,9 @@ public class EvmProcessor : BaseProcessor return _evmManager.GetFee(Constants.Chains.Arbitrum).Result; } - public override decimal GetPrice(Account account, Ticker ticker, DateTime date) + public override async Task GetPrice(Account account, Ticker ticker, DateTime date) { - return GetCandle(account, ticker, date).Close; + return (await GetCandles(account, ticker, date, Timeframe.OneMinute, true)).Last().Close; } public override async Task GetQuantityInPosition(Account account, Ticker ticker) diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs deleted file mode 100644 index 79d2ada..0000000 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs +++ /dev/null @@ -1,220 +0,0 @@ -using CryptoExchange.Net.Authentication; -using FTX.Net.Interfaces.Clients; -using FTX.Net.Objects; -using Managing.Common; -using Managing.Domain.Accounts; -using Managing.Domain.Candles; -using Managing.Domain.Statistics; -using Managing.Domain.Trades; -using Managing.Infrastructure.Exchanges.Helpers; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Exchanges.Exchanges; - -public class FtxProcessor : BaseProcessor -{ - private IFTXClient _ftxClient; - private ILogger _logger; - - public FtxProcessor(ILogger logger) - { - _logger = logger; - } - - public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Ftx; - - public override void LoadClient(Account account) - { - var credentials = new ApiCredentials(account.Key, account.Secret); - - var ftxConfig = new FTXClientOptions() - { - SubaccountName = account.Name - }; - } - - - public override async Task CancelOrder(Account account, Ticker ticker) - { - LoadClient(account); - var ftxResult = await _ftxClient.TradeApi.Trading.CancelAllOrdersAsync(FtxHelpers.ToFtxTicker(ticker)); - return ftxResult.Success; - } - - public override async Task GetBalance(Account account, bool isForPaperTrading = false) - { - LoadClient(account); - - var balance = 0m; - var ftxBalance = await _ftxClient.TradeApi.Account.GetBalancesAsync(); - foreach (var item in ftxBalance.Data) - { - balance += item.UsdValue; - } - - return balance; - } - - public override Candle GetCandle(Account account, Ticker ticker, DateTime date) - { - LoadClient(account); - - var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), - FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; - if (ftxKlines != null && ftxKlines.Any()) - { - var lastCandle = ftxKlines.ToList().LastOrDefault(); - return CandleHelpers.Map(lastCandle); - } - - return null; - } - - public override async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, - Timeframe timeframe) - { - LoadClient(account); - - var candles = new List(); - var ftxCandles = await _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), - FtxHelpers.Map(timeframe), startDate); - - if (ftxCandles.Success) - candles.AddRange(ftxCandles.Data.SkipLast(1) - .Select(ftxKline => FtxHelpers.Map(ftxKline, ticker, account.Exchange, timeframe))); - - return candles; - } - - public override decimal GetFee(Account account, bool isForPaperTrading = false) - { - LoadClient(account); - var ftxResult = _ftxClient.TradeApi.Account.GetAccountInfoAsync().Result.Data; - return ftxResult.TakerFee; - } - - public override decimal GetPrice(Account account, Ticker ticker, DateTime date) - { - LoadClient(account); - var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), - FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; - if (ftxKlines != null && ftxKlines.Any()) - { - return ftxKlines.ToList().LastOrDefault().ClosePrice; - } - - return 0; - } - - public override async Task GetQuantityInPosition(Account account, Ticker ticker) - { - var ftxTickerBalance = _ftxClient.TradeApi.Account.GetPositionsAsync().Result.Data; - return ftxTickerBalance.FirstOrDefault(f => f.Future == FtxHelpers.ToFtxTicker(ticker)).Quantity; - } - - public override async Task GetTrade(Account account, string order, Ticker ticker) - { - LoadClient(account); - var ftxOrder = await _ftxClient.TradeApi.Trading.GetOrderByClientOrderIdAsync(order); - return FtxHelpers.Map(ftxOrder.Data); - } - - public override async Task> GetTrades(Account account, Ticker ticker) - { - LoadClient(account); - var ftxOrder = await _ftxClient.TradeApi.Trading.GetOrdersAsync(ticker.ToString()); - return (List)ftxOrder.Data.Select(o => FtxHelpers.Map(o)); - } - - public override decimal GetVolume(Account account, Ticker ticker) - { - var futureStats = _ftxClient.TradeApi.ExchangeData.GetFutureStatsAsync(FtxHelpers.ToFtxTicker(ticker)).Result - .Data; - return futureStats.Volume; - } - - public override async Task OpenTrade(Account account, Ticker ticker, TradeDirection direction, decimal price, - decimal quantity, decimal? leverage = null, TradeType tradeType = TradeType.Limit, bool reduceOnly = false, - bool isForPaperTrading = false, DateTime? currentDate = null, bool ioc = true, - decimal? stopLossPrice = null, - decimal? takeProfitPrice = null) - { - LoadClient(account); - - var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, - leverage, "", ""); - - trade.SetQuantity(quantity, 6); - - Trade ftxOrder; - if (tradeType == TradeType.StopLoss || tradeType == TradeType.TakeProfitLimit || - tradeType == TradeType.StopMarket) - { - var ftxTriggerOrderType = FtxHelpers.FtxTriggerOrderTypeMap(tradeType); - var ftxResult = await _ftxClient.TradeApi.Trading.PlaceTriggerOrderAsync(FtxHelpers.ToFtxTicker(ticker), - direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy, - ftxTriggerOrderType, - triggerPrice: price, - reduceOnly: true, - retryUntilFilled: false, - quantity: quantity); - _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult)); - ftxOrder = FtxHelpers.Map(ftxResult, leverage); - } - else - { - var ftxOrderType = FtxHelpers.FtxOrderTypeMap(tradeType); - var ftxResult = await _ftxClient.TradeApi.Trading.PlaceOrderAsync(FtxHelpers.ToFtxTicker(ticker), - direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy, - ftxOrderType, - price: ftxOrderType == FTX.Net.Enums.OrderType.Limit ? price : null, - quantity: quantity, - clientOrderId: Guid.NewGuid().ToString(), - immediateOrCancel: ioc); - _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult)); - ftxOrder = FtxHelpers.Map(ftxResult, leverage); - } - - trade.SetStatus(ftxOrder.Status); - trade.SetExchangeOrderId(ftxOrder.ExchangeOrderId); - trade.SetMessage(ftxOrder.Message); - trade.Price = ftxOrder.Price; - - return trade; - } - - public override Orderbook GetOrderbook(Account account, Ticker ticker) - { - LoadClient(account); - var ftxOrderBook = _ftxClient.TradeApi.ExchangeData.GetOrderBookAsync(FtxHelpers.ToFtxTicker(ticker), 100) - .Result; - return FtxHelpers.Map(ftxOrderBook); - } - - public override Task> GetBalances(Account account, bool isForPaperTrading = false) - { - throw new NotImplementedException(); - } - - public override Task> GetOrders(Account acount, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task GetTrade(string reference, string orderId, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task> GetFundingRates() - { - throw new NotImplementedException(); - } - - public override Task> GetPositions(Account account) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs deleted file mode 100644 index b2cfcae..0000000 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs +++ /dev/null @@ -1,156 +0,0 @@ -using CryptoExchange.Net.Authentication; -using Kraken.Net.Clients; -using Kraken.Net.Interfaces.Clients; -using Kraken.Net.Objects.Options; -using Managing.Common; -using Managing.Domain.Accounts; -using Managing.Domain.Candles; -using Managing.Domain.Statistics; -using Managing.Domain.Trades; -using Managing.Infrastructure.Exchanges.Helpers; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Exchanges.Exchanges; - -public class KrakenProcessor : BaseProcessor -{ - private ILogger _logger; - private IKrakenRestClient _krakenClient; - - public KrakenProcessor(ILogger logger) - { - _logger = logger; - } - - public override Task CancelOrder(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Kraken; - - public override async Task GetBalance(Account account, bool isForPaperTrading = false) - { - LoadClient(account); - var balance = await _krakenClient.SpotApi.Account.GetBalancesAsync(); - balance.Data.TryGetValue("USDT", out decimal krakenBalance); - return krakenBalance; - } - - public override Task> GetBalances(Account account, bool isForPaperTrading = false) - { - throw new NotImplementedException(); - } - - public override Candle GetCandle(Account account, Ticker ticker, DateTime date) - { - throw new NotImplementedException(); - } - - public override Task> GetCandles(Account account, Ticker ticker, DateTime startDate, - Timeframe interval) - { - throw new NotImplementedException(); - } - - public override decimal GetFee(Account account, bool isForPaperTrading = false) - { - throw new NotImplementedException(); - } - - public override Orderbook GetOrderbook(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task> GetOrders(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override decimal GetPrice(Account account, Ticker ticker, DateTime date) - { - LoadClient(account); - var krakenKline = _krakenClient.SpotApi.ExchangeData.GetKlinesAsync(ticker.ToString(), - Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0]; - return (krakenKline.HighPrice + krakenKline.ClosePrice) / 2; - } - - public override Task GetQuantityInPosition(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override async Task GetTrade(Account account, string order, Ticker ticker) - { - LoadClient(account); - var krakenOrder = await _krakenClient.SpotApi.Trading.GetOrderAsync(order); - return KrakenHelpers.Map(krakenOrder.Data.Values.First()); - } - - public override Task GetTrade(string reference, string orderId, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override Task> GetFundingRates() - { - throw new NotImplementedException(); - } - - public override Task> GetPositions(Account account) - { - throw new NotImplementedException(); - } - - public override async Task> GetTrades(Account account, Ticker ticker) - { - LoadClient(account); - var krakenOrder = await _krakenClient.SpotApi.Trading.GetOrdersAsync(); - return (List)krakenOrder.Data.Select(o => KrakenHelpers.Map(o)); - } - - public override decimal GetVolume(Account account, Ticker ticker) - { - throw new NotImplementedException(); - } - - public override void LoadClient(Account account) - { - var credentials = new ApiCredentials(account.Key, account.Secret); - var krakenConfig = new KrakenRestOptions() - { - ApiCredentials = credentials - }; - _krakenClient = new KrakenRestClient((options) => { options.ApiCredentials = krakenConfig.ApiCredentials; }); - } - - public override async Task OpenTrade(Account account, Ticker ticker, TradeDirection direction, decimal price, - decimal quantity, decimal? leverage = null, TradeType tradeType = TradeType.Limit, bool reduceOnly = false, - bool isForPaperTrading = false, DateTime? currentDate = null, bool ioc = true, - decimal? stopLossPrice = null, - decimal? takeProfitPrice = null) - { - LoadClient(account); - var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, - leverage, "", ""); - trade.SetQuantity(quantity, 6); - trade.SetPrice(price, 1); - - var order = await _krakenClient.SpotApi.Trading.PlaceOrderAsync(ticker.ToString(), - direction != TradeDirection.Long ? Kraken.Net.Enums.OrderSide.Sell : Kraken.Net.Enums.OrderSide.Buy, - KrakenHelpers.KrakenOrderTypeMap(tradeType), - price: price, - quantity: quantity, - leverage: leverage); - - _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(order)); - var krakenOrderExecuted = GetTrade(account, ((string[])order.Data.OrderIds)[0], ticker).Result; - trade.SetStatus(krakenOrderExecuted.Status); - trade.SetExchangeOrderId(krakenOrderExecuted.ExchangeOrderId); - trade.SetMessage(krakenOrderExecuted.Message); - return trade; - } -} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs b/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs index 965e394..4871738 100644 --- a/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs +++ b/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs @@ -2,7 +2,6 @@ using Binance.Net.Interfaces; using Binance.Net.Objects.Models.Futures; using CryptoExchange.Net.Objects; -using Managing.Common; using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.Trades; @@ -73,22 +72,18 @@ namespace Managing.Infrastructure.Exchanges.Helpers "", result.Error?.Message); } - public static Candle Map(IBinanceKline binanceKline, Ticker ticker, Enums.TradingExchanges exchange) + public static Candle Map(IBinanceKline binanceKline, Ticker ticker, TradingExchanges exchange) { return new Candle { Date = binanceKline.CloseTime, - BaseVolume = binanceKline.Volume, + Volume = binanceKline.Volume, Close = binanceKline.ClosePrice, High = binanceKline.HighPrice, Low = binanceKline.LowPrice, Open = binanceKline.OpenPrice, Ticker = ticker.ToString(), - QuoteVolume = binanceKline.QuoteVolume, - TradeCount = binanceKline.TradeCount, OpenTime = binanceKline.OpenTime, - TakerBuyBaseVolume = binanceKline.TakerBuyBaseVolume, - TakerBuyQuoteVolume = binanceKline.TakerBuyQuoteVolume, Exchange = exchange }; } diff --git a/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs b/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs index 7f18f97..ac4dc67 100644 --- a/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs +++ b/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs @@ -1,7 +1,6 @@ using CryptoExchange.Net.Objects; using FTX.Net.Enums; using FTX.Net.Objects.Models; -using Managing.Common; using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.Trades; @@ -178,13 +177,13 @@ namespace Managing.Infrastructure.Exchanges.Helpers public static Candle Map( FTXKline ftxKline, Ticker ticker, - Enums.TradingExchanges exchange, + TradingExchanges exchange, Timeframe timeframe) { return new Candle { Date = ftxKline.OpenTime, - BaseVolume = ftxKline.Volume ?? 0, + Volume = ftxKline.Volume ?? 0, Close = ftxKline.ClosePrice, High = ftxKline.HighPrice, Low = ftxKline.LowPrice, diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs index ac3e4f0..6c520f5 100644 --- a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs @@ -303,7 +303,7 @@ namespace Managing.Infrastructure.Messengers.Discord var tradingService = (ITradingService)_services.GetService(typeof(ITradingService)); await component.RespondAsync("Alright, let met few seconds to close this position"); - var position = _tradingService.GetPositionByIdentifier(parameters[1]); + var position = await tradingService.GetPositionByIdentifierAsync(parameters[1]); var command = new ClosePositionCommand(position); var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command); @@ -477,10 +477,27 @@ namespace Managing.Infrastructure.Messengers.Discord public async Task SendTradeMessage(string message, bool isBadBehavior = false) { - var channel = - _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as - IMessageChannel; - await channel.SendMessageAsync(message); + try + { + var channel = + _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as + IMessageChannel; + + if (channel == null) + { + _logger.LogError("Discord channel not found: {ChannelId}", + isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId); + return; + } + else + { + await channel?.SendMessageAsync(message)!; + } + } + catch (Exception e) + { + Console.WriteLine(e); + } } public async Task SendClosedPosition(string address, Trade oldTrade) diff --git a/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj b/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj index d6c37e5..82f93c3 100644 --- a/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj +++ b/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj @@ -1,19 +1,19 @@ - - net8.0 - enable - + + net8.0 + enable + - - - - - - + + + + + + - - - + + + diff --git a/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj b/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj index 0ab30e7..f90b07f 100644 --- a/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj +++ b/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj @@ -1,19 +1,19 @@ - - net8.0 - enable - AnyCPU;x64 - + + net8.0 + enable + AnyCPU;x64 + - - - - - + + + + + - - - + + + diff --git a/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs b/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs index 494bc52..7df8093 100644 --- a/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs +++ b/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs @@ -1,5 +1,4 @@ -using Managing.Application.Abstractions; -using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Candles; using Managing.Domain.Trades; @@ -42,12 +41,12 @@ namespace Managing.Infrastructure.Tests [Theory] [InlineData(TradingExchanges.Evm, Ticker.BTC)] - public void Should_Return_Price_For_Given_Ticker(TradingExchanges exchange, Ticker ticker) + public async void Should_Return_Price_For_Given_Ticker(TradingExchanges exchange, Ticker ticker) { var account = PrivateKeys.GetAccount(); - var price = _exchangeService.GetPrice(account, ticker, DateTime.Now); + var price = await _exchangeService.GetPrice(account, ticker, DateTime.Now); Assert.IsType(price); - Assert.InRange(price, 0, 1000000); + Assert.InRange(price, 0, 1000000); } [Theory] @@ -57,10 +56,10 @@ namespace Managing.Infrastructure.Tests var account = PrivateKeys.GetAccount(); var candle = _exchangeService.GetCandle(account, ticker, DateTime.Now); Assert.IsType(candle); - Assert.InRange(candle.High, 0, 1000000); - Assert.InRange(candle.Low, 0, 1000000); - Assert.InRange(candle.Open, 0, 1000000); - Assert.InRange(candle.Close, 0, 1000000); + Assert.InRange(candle.High, 0, 1000000); + Assert.InRange(candle.Low, 0, 1000000); + Assert.InRange(candle.Open, 0, 1000000); + Assert.InRange(candle.Close, 0, 1000000); } [Theory] @@ -169,15 +168,15 @@ namespace Managing.Infrastructure.Tests [InlineData(TradingExchanges.Evm, Ticker.BTC, 0.1, TradeDirection.Long)] [InlineData(TradingExchanges.Evm, Ticker.BTC, 700, TradeDirection.Long)] [InlineData(TradingExchanges.Evm, Ticker.BTC, 700, TradeDirection.Short)] - public void Should_Return_Best_Price( + public async void Should_Return_Best_Price( TradingExchanges exchange, Ticker ticker, decimal quantity, TradeDirection direction) { var account = PrivateKeys.GetAccount(); - var lastPrice = _exchangeService.GetPrice(account, ticker, DateTime.UtcNow); - var bestPrice = _exchangeService.GetBestPrice(account, ticker, lastPrice, quantity, direction); + var lastPrice = await _exchangeService.GetPrice(account, ticker, DateTime.UtcNow); + var bestPrice = await _exchangeService.GetBestPrice(account, ticker, lastPrice, quantity, direction); Assert.IsType(bestPrice); diff --git a/src/Managing.Infrastructure.Tests/SynthPredictionTests.cs b/src/Managing.Infrastructure.Tests/SynthPredictionTests.cs index 0431aec..77f295f 100644 --- a/src/Managing.Infrastructure.Tests/SynthPredictionTests.cs +++ b/src/Managing.Infrastructure.Tests/SynthPredictionTests.cs @@ -38,8 +38,7 @@ public class SynthPredictionTests High = price * 1.001m, Low = price * 0.998m, Close = price, - BaseVolume = 1000m, - QuoteVolume = price * 1000m + Volume = price * 1000m }; return new Signal( diff --git a/src/Managing.Infrastructure.Web3/EvmManager.cs b/src/Managing.Infrastructure.Web3/EvmManager.cs index 1a37e04..c2f9074 100644 --- a/src/Managing.Infrastructure.Web3/EvmManager.cs +++ b/src/Managing.Infrastructure.Web3/EvmManager.cs @@ -371,7 +371,7 @@ public class EvmManager : IEvmManager if (gmxPrices == null) return null; - var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.ToUnixTimestamp()).ToList(); + var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.AddMinutes(-1).ToUnixTimestamp()).ToList(); var candles = new List(); var timeBetweenCandles = gmxPrices.Candles.Count > 2 ? gmxPrices.Candles[0][0] - gmxPrices.Candles[1][0] : 900; // Default 15 minutes diff --git a/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj b/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj index d7018a2..4e4d535 100644 --- a/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj +++ b/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj @@ -12,8 +12,8 @@ - - + + @@ -24,7 +24,7 @@ - + diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 9e0b675..5ec370a 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -222,7 +222,10 @@ export async function getClientForAddress( "0x0e46941F9bfF8d0784BFfa3d0D7883CDb82D7aE7": { isListed: false, }, - "0x9c060B2fA953b5f69879a8B7B81f62BFfEF360be": { + "0x4C0Bb704529Fa49A26bD854802d70206982c6f1B": { + isListed: false, + }, + "0x672fEA44f4583DdaD620d60C1Ac31021F47558Cb": { isListed: false, }, } @@ -512,7 +515,7 @@ export const closeGmxPositionImpl = async ( ): Promise => { try { // Get markets and tokens data from GMX SDK with cache - const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); if (!marketsInfoData || !tokensData) { throw new Error("No markets or tokens info data"); @@ -758,7 +761,7 @@ export async function getGmxTrade( export const getGmxPositionsImpl = async ( sdk: GmxSdk ): Promise => { - const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); const positionsInfo = await sdk.positions.getPositionsInfo({ marketsInfoData, diff --git a/src/Managing.Web3Proxy/test/plugins/close-position.test.ts b/src/Managing.Web3Proxy/test/plugins/close-position.test.ts index 3e75670..ea8a248 100644 --- a/src/Managing.Web3Proxy/test/plugins/close-position.test.ts +++ b/src/Managing.Web3Proxy/test/plugins/close-position.test.ts @@ -1,16 +1,16 @@ import {test} from 'node:test' import assert from 'node:assert' import {closeGmxPositionImpl, getClientForAddress} from '../../src/plugins/custom/gmx' -import {Ticker, TradeDirection} from '../../src/generated/ManagingApiTypes' +import {TradeDirection} from '../../src/generated/ManagingApiTypes' test('GMX Position Closing', async (t) => { await t.test('should close a long position for BTC', async () => { - const sdk = await getClientForAddress('0xbBA4eaA534cbD0EcAed5E2fD6036Aec2E7eE309f') + const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78') const result = await closeGmxPositionImpl( sdk, - Ticker.SOL, - TradeDirection.Long + "SUI", + TradeDirection.Short ) console.log('Position closing result:', result) assert.ok(result, 'Position closing result should be defined') diff --git a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx index 3442586..efa9b09 100644 --- a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx +++ b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx @@ -1,22 +1,32 @@ -import { StatusOfflineIcon } from '@heroicons/react/solid' -import type { SubmitHandler } from 'react-hook-form' -import { useForm } from 'react-hook-form' -import { usePrivy, useSignMessage } from '@privy-io/react-auth' +import {StatusOfflineIcon} from '@heroicons/react/solid' +import type {SubmitHandler} from 'react-hook-form' +import {useForm} from 'react-hook-form' +import {usePrivy, useSignMessage} from '@privy-io/react-auth' +import {useEffect} from 'react' import useApiUrlStore from '../../../app/store/apiStore' -import { UserClient } from '../../../generated/ManagingApi' -import type { ILoginFormInput } from '../../../global/type' +import {UserClient} from '../../../generated/ManagingApi' +import type {ILoginFormInput} from '../../../global/type' import useCookie from '../../../hooks/useCookie' -import { SecondaryNavbar } from '../NavBar/NavBar' +import {SecondaryNavbar} from '../NavBar/NavBar' import Toast from '../Toast/Toast' const LogIn = () => { const { apiUrl } = useApiUrlStore() - const { register, handleSubmit } = useForm() + const { register, handleSubmit, setValue } = useForm() const { user, logout, ready, authenticated } = usePrivy() const { signMessage } = useSignMessage() const { setCookie } = useCookie() + // Prefill the name field with the Privy DID when user is available + useEffect(() => { + if (user?.id) { + console.log(user) + + setValue('name', user.id) + } + }, [user?.id, setValue]) + const onSubmit: SubmitHandler = async (form) => { if (!authenticated || !user || !user.wallet?.address) { const t = new Toast('Error: Not authenticated') diff --git a/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx b/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx index dd8a07b..1b60731 100644 --- a/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx +++ b/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx @@ -92,7 +92,7 @@ const TradesModal: React.FC = ({ ) : strategyData ? (
-

Strategy: {strategyData.strategyName}

+

Strategy: {strategyData.name}

Win Rate: {strategyData.winRate?.toFixed(2)}%

PnL: {strategyData.pnL?.toFixed(2)} $

Wins: {strategyData.wins} / Losses: {strategyData.losses}

@@ -122,10 +122,10 @@ const TradesModal: React.FC = ({ {position.originDirection} {position.status} - {position.open.price.toFixed(2)} - {position.open.quantity.toFixed(4)} - 0 ? 'text-success' : 'text-error'}> - {position.profitAndLoss?.realized?.toFixed(2) || '0.00'} $ + {position.Open.price.toFixed(2)} + {position.Open.quantity.toFixed(4)} + 0 ? 'text-success' : 'text-error'}> + {position.ProfitAndLoss?.realized?.toFixed(2) || '0.00'} $ {position.status !== 'Finished' && ( diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx index e94745b..91bd88e 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx @@ -17,8 +17,8 @@ import { Timeframe, TradingBotConfigRequest, } from '../../../generated/ManagingApi' -import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type' -import {Loader, Slider} from '../../atoms' +import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type.tsx' +import {Loader} from '../../atoms' import {Modal, Toast} from '../../mollecules' import FormInput from '../../mollecules/FormInput/FormInput' import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement' @@ -169,7 +169,6 @@ const BacktestModal: React.FC = ({ loopbackPeriod: customScenario.loopbackPeriod } : undefined, timeframe: form.timeframe, - botType: form.botType, isForWatchingOnly: false, // Always false for backtests cooldownPeriod: form.cooldownPeriod || 1, maxLossStreak: form.maxLossStreak || 0, @@ -181,7 +180,8 @@ const BacktestModal: React.FC = ({ useSynthApi: form.useSynthApi ?? false, useForPositionSizing: form.useForPositionSizing ?? true, useForSignalFiltering: form.useForSignalFiltering ?? true, - useForDynamicStopLoss: form.useForDynamicStopLoss ?? true + useForDynamicStopLoss: form.useForDynamicStopLoss ?? true, + flipPosition: false }; // Create the RunBacktestRequest @@ -189,11 +189,7 @@ const BacktestModal: React.FC = ({ config: tradingBotConfigRequest, // Use the request object startDate: new Date(form.startDate), endDate: new Date(form.endDate), - balance: form.balance, - watchOnly: false, - save: form.save || false, - moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement, - moneyManagement: customMoneyManagement + save: form.save || false }; const backtest = await backtestClient.backtest_Run(request); @@ -578,29 +574,6 @@ const BacktestModal: React.FC = ({
- {/* Loop Slider (if enabled) */} - {showLoopSlider && ( - - Loop -
- i -
-
- } - htmlFor="loop" - > - setLoopQuantity(Number(e.target.value))} - > - - )} - {/* Max Loss Streak & Max Position Time */}
= ({ const config = currentBacktest.config; // Helper function to calculate position open time in hours - const calculateOpenTimeInHours = (position: any) => { - const openDate = new Date(position.open.date); + const calculateOpenTimeInHours = (position: Position) => { + const openDate = new Date(position.Open.date); let closeDate: Date | null = null; // Determine close date based on realized P&L (matching backend logic) - if (position.profitAndLoss?.realized != null) { - if (position.profitAndLoss.realized > 0) { + if (position.ProfitAndLoss?.realized != null) { + if (position.ProfitAndLoss.realized > 0) { // Profitable close = Take Profit - closeDate = new Date(position.takeProfit1.date); + closeDate = new Date(position.TakeProfit1.date); } else { // Loss or breakeven close = Stop Loss - closeDate = new Date(position.stopLoss.date); + closeDate = new Date(position.StopLoss.date); } } @@ -117,7 +118,7 @@ const BacktestRowDetails: React.FC = ({ // Calculate average open time for winning positions const getAverageOpenTimeWinning = () => { const winningPositions = positions.filter((p) => { - const realized = p.profitAndLoss?.realized ?? 0; + const realized = p.ProfitAndLoss?.realized ?? 0; return realized > 0; }); @@ -134,7 +135,7 @@ const BacktestRowDetails: React.FC = ({ // Calculate average open time for losing positions const getAverageOpenTimeLosing = () => { const losingPositions = positions.filter((p) => { - const realized = p.profitAndLoss?.realized ?? 0; + const realized = p.ProfitAndLoss?.realized ?? 0; return realized <= 0; }); @@ -151,7 +152,7 @@ const BacktestRowDetails: React.FC = ({ // Calculate maximum open time for winning positions const getMaxOpenTimeWinning = () => { const winningPositions = positions.filter((p) => { - const realized = p.profitAndLoss?.realized ?? 0; + const realized = p.ProfitAndLoss?.realized ?? 0; return realized > 0; }); @@ -183,19 +184,19 @@ const BacktestRowDetails: React.FC = ({ positions.forEach((position) => { // Calculate volume for open trade - const openLeverage = position.open.leverage || 1; - const openVolume = position.open.quantity * position.open.price * openLeverage; + const openLeverage = position.Open.leverage || 1; + const openVolume = position.Open.quantity * position.Open.price * openLeverage; totalVolume += openVolume; // Calculate volume for close trade (stopLoss or takeProfit based on realized P&L) - if (position.profitAndLoss?.realized != null) { + if (position.ProfitAndLoss?.realized != null) { let closeTrade; - if (position.profitAndLoss.realized > 0) { + if (position.ProfitAndLoss.realized > 0) { // Profitable close = Take Profit - closeTrade = position.takeProfit1; + closeTrade = position.TakeProfit1; } else { // Loss or breakeven close = Stop Loss - closeTrade = position.stopLoss; + closeTrade = position.StopLoss; } if (closeTrade) { @@ -226,8 +227,8 @@ const BacktestRowDetails: React.FC = ({ const candleTimeframeMs = new Date(candles[1].date).getTime() - new Date(candles[0].date).getTime(); const sortedPositions = [...positions].sort((a, b) => { - const dateA = new Date(a.open.date).getTime(); - const dateB = new Date(b.open.date).getTime(); + const dateA = new Date(a.Open.date).getTime(); + const dateB = new Date(b.Open.date).getTime(); return dateA - dateB; }); @@ -237,23 +238,23 @@ const BacktestRowDetails: React.FC = ({ const currentPosition = sortedPositions[i]; const nextPosition = sortedPositions[i + 1]; - const currentRealized = currentPosition.profitAndLoss?.realized ?? 0; - const nextRealized = nextPosition.profitAndLoss?.realized ?? 0; + const currentRealized = currentPosition.ProfitAndLoss?.realized ?? 0; + const nextRealized = nextPosition.ProfitAndLoss?.realized ?? 0; // Check if current position is winning and next position is losing if (currentRealized > 0 && nextRealized <= 0) { // Calculate the close time of the current (winning) position let currentCloseDate: Date | null = null; - if (currentPosition.profitAndLoss?.realized != null) { - if (currentPosition.profitAndLoss.realized > 0) { - currentCloseDate = new Date(currentPosition.takeProfit1.date); + if (currentPosition.ProfitAndLoss?.realized != null) { + if (currentPosition.ProfitAndLoss.realized > 0) { + currentCloseDate = new Date(currentPosition.TakeProfit1.date); } else { - currentCloseDate = new Date(currentPosition.stopLoss.date); + currentCloseDate = new Date(currentPosition.StopLoss.date); } } if (currentCloseDate) { - const nextOpenDate = new Date(nextPosition.open.date); + const nextOpenDate = new Date(nextPosition.Open.date); const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime(); if (gapInMs >= 0) { // Only consider positive gaps @@ -298,7 +299,7 @@ const BacktestRowDetails: React.FC = ({ if (positions.length === 0) return "0.00"; // Get all trade dates and sort them - const tradeDates = positions.map(position => new Date(position.open.date)).sort((a, b) => a.getTime() - b.getTime()); + const tradeDates = positions.map(position => new Date(position.Open.date)).sort((a, b) => a.getTime() - b.getTime()); if (tradeDates.length < 2) return positions.length.toString(); @@ -327,14 +328,14 @@ const BacktestRowDetails: React.FC = ({ { - const realized = p.profitAndLoss?.realized ?? 0 + const realized = p.ProfitAndLoss?.realized ?? 0 return realized > 0 ? p : null })} > { - const realized = p.profitAndLoss?.realized ?? 0 + const realized = p.ProfitAndLoss?.realized ?? 0 return realized <= 0 ? p : null })} > @@ -371,12 +372,6 @@ const BacktestRowDetails: React.FC = ({ (config.moneyManagement?.takeProfit * 100).toFixed(2) + "%" + " Lev.: x" + config.moneyManagement?.leverage } > - () const {themeProperty} = useTheme() const theme = themeProperty() - console.log(theme) const series1 = useRef>() const [timeDiff, setTimeDiff] = useState(0) const [candleCount, setCandleCount] = useState(candles.length) @@ -220,7 +219,7 @@ const TradeChart = ({ const negativeColor = theme.error const positiveColor = theme.success const status = position.status - const realized = position.profitAndLoss?.realized ?? 0 + const realized = position.ProfitAndLoss?.realized ?? 0 if (status != undefined) { if ( @@ -376,20 +375,19 @@ const TradeChart = ({ getPositionColor(p), p.originDirection, p.date, - p.open.price.toString() + p.Open?.price?.toString() ) ) markers.push(...positionMarkers) const lastPositionOpen = positions[positions.length - 1] - if (lastPositionOpen) { series1.current.createPriceLine( - buildLine(theme.error, lastPositionOpen.stopLoss.price, 'SL') + buildLine(theme.error, lastPositionOpen.StopLoss?.price, 'SL') ) series1.current.createPriceLine( - buildLine(theme.success, lastPositionOpen.takeProfit1.price, 'TP') + buildLine(theme.success, lastPositionOpen.TakeProfit1?.price, 'TP') ) } } diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index 8d12caf..0bc8926 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -2659,14 +2659,14 @@ export class SettingsClient extends AuthorizedApiBase { this.baseUrl = baseUrl ?? "http://localhost:5000"; } - settings_SetupSettings(): Promise { + settings_SetupSettings(): Promise { let url_ = this.baseUrl + "/Settings"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { method: "POST", headers: { - "Accept": "application/octet-stream" + "Accept": "application/json" } }; @@ -2677,26 +2677,21 @@ export class SettingsClient extends AuthorizedApiBase { }); } - protected processSettings_SetupSettings(response: Response): Promise { + protected processSettings_SetupSettings(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; - if (status === 200 || status === 206) { - const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; - let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; - let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; - if (fileName) { - fileName = decodeURIComponent(fileName); - } else { - fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; - fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; - } - return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as boolean; + return result200; + }); } else if (status !== 200 && status !== 204) { return response.text().then((_responseText) => { return throwException("An unexpected server error occurred.", status, _responseText, _headers); }); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } settings_ResetSettings(): Promise { @@ -3658,14 +3653,13 @@ export interface Backtest { hodlPercentage: number; config: TradingBotConfig; positions: Position[]; - signals: Signal[]; + signals: LightSignal[]; candles: Candle[]; startDate: Date; endDate: Date; statistics: PerformanceMetrics; fees: number; walletBalances: KeyValuePairOfDateTimeAndDecimal[]; - optimizedMoneyManagement: MoneyManagement; user: User; indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; }; score: number; @@ -3793,11 +3787,11 @@ export interface Position { originDirection: TradeDirection; ticker: Ticker; moneyManagement: MoneyManagement; - open: Trade; - stopLoss: Trade; - takeProfit1: Trade; - takeProfit2?: Trade | null; - profitAndLoss?: ProfitAndLoss | null; + Open: Trade; + StopLoss: Trade; + TakeProfit1: Trade; + TakeProfit2?: Trade | null; + ProfitAndLoss?: ProfitAndLoss | null; status: PositionStatus; signalIdentifier?: string | null; identifier: string; @@ -3812,7 +3806,7 @@ export enum TradeDirection { } export interface Trade { - fee?: number; + fee: number; date: Date; direction: TradeDirection; status: TradeStatus; @@ -3820,9 +3814,9 @@ export interface Trade { ticker: Ticker; quantity: number; price: number; - leverage?: number; + leverage: number; exchangeOrderId: string; - message?: string | null; + message: string; } export enum TradeStatus { @@ -3876,7 +3870,7 @@ export enum PositionInitiator { export interface ValueObject { } -export interface Signal extends ValueObject { +export interface LightSignal extends ValueObject { status: SignalStatus; direction: TradeDirection; confidence: Confidence; @@ -3888,7 +3882,6 @@ export interface Signal extends ValueObject { exchange: TradingExchanges; indicatorType: IndicatorType; signalType: SignalType; - user?: User | null; indicatorName: string; } @@ -3912,15 +3905,10 @@ export interface Candle { date: Date; open: number; close: number; - volume?: number; high: number; low: number; - baseVolume?: number; - quoteVolume?: number; - tradeCount?: number; - takerBuyBaseVolume?: number; - takerBuyQuoteVolume?: number; timeframe: Timeframe; + volume?: number; } export interface PerformanceMetrics { @@ -4127,7 +4115,7 @@ export interface BundleBacktestRequest { status: BundleBacktestRequestStatus; name: string; backtestRequestsJson: string; - results?: Backtest[] | null; + results?: string[] | null; totalBacktests: number; completedBacktests: number; failedBacktests: number; @@ -4251,7 +4239,7 @@ export enum BotType { export interface TradingBotResponse { status: string; - signals: Signal[]; + signals: LightSignal[]; positions: Position[]; candles: Candle[]; winRate: number; @@ -4299,11 +4287,11 @@ export interface Spotlight { export interface TickerSignal { ticker: Ticker; - fiveMinutes: Signal[]; - fifteenMinutes: Signal[]; - oneHour: Signal[]; - fourHour: Signal[]; - oneDay: Signal[]; + fiveMinutes: LightSignal[]; + fifteenMinutes: LightSignal[]; + oneHour: LightSignal[]; + fourHour: LightSignal[]; + oneDay: LightSignal[]; } export interface CandlesWithIndicatorsResponse { diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 6ee1042..fa06b4b 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -225,14 +225,13 @@ export interface Backtest { hodlPercentage: number; config: TradingBotConfig; positions: Position[]; - signals: Signal[]; + signals: LightSignal[]; candles: Candle[]; startDate: Date; endDate: Date; statistics: PerformanceMetrics; fees: number; walletBalances: KeyValuePairOfDateTimeAndDecimal[]; - optimizedMoneyManagement: MoneyManagement; user: User; indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; }; score: number; @@ -360,11 +359,11 @@ export interface Position { originDirection: TradeDirection; ticker: Ticker; moneyManagement: MoneyManagement; - open: Trade; - stopLoss: Trade; - takeProfit1: Trade; - takeProfit2?: Trade | null; - profitAndLoss?: ProfitAndLoss | null; + Open: Trade; + StopLoss: Trade; + TakeProfit1: Trade; + TakeProfit2?: Trade | null; + ProfitAndLoss?: ProfitAndLoss | null; status: PositionStatus; signalIdentifier?: string | null; identifier: string; @@ -379,7 +378,7 @@ export enum TradeDirection { } export interface Trade { - fee?: number; + fee: number; date: Date; direction: TradeDirection; status: TradeStatus; @@ -387,9 +386,9 @@ export interface Trade { ticker: Ticker; quantity: number; price: number; - leverage?: number; + leverage: number; exchangeOrderId: string; - message?: string | null; + message: string; } export enum TradeStatus { @@ -443,7 +442,7 @@ export enum PositionInitiator { export interface ValueObject { } -export interface Signal extends ValueObject { +export interface LightSignal extends ValueObject { status: SignalStatus; direction: TradeDirection; confidence: Confidence; @@ -455,7 +454,6 @@ export interface Signal extends ValueObject { exchange: TradingExchanges; indicatorType: IndicatorType; signalType: SignalType; - user?: User | null; indicatorName: string; } @@ -479,15 +477,10 @@ export interface Candle { date: Date; open: number; close: number; - volume?: number; high: number; low: number; - baseVolume?: number; - quoteVolume?: number; - tradeCount?: number; - takerBuyBaseVolume?: number; - takerBuyQuoteVolume?: number; timeframe: Timeframe; + volume?: number; } export interface PerformanceMetrics { @@ -694,7 +687,7 @@ export interface BundleBacktestRequest { status: BundleBacktestRequestStatus; name: string; backtestRequestsJson: string; - results?: Backtest[] | null; + results?: string[] | null; totalBacktests: number; completedBacktests: number; failedBacktests: number; @@ -818,7 +811,7 @@ export enum BotType { export interface TradingBotResponse { status: string; - signals: Signal[]; + signals: LightSignal[]; positions: Position[]; candles: Candle[]; winRate: number; @@ -866,11 +859,11 @@ export interface Spotlight { export interface TickerSignal { ticker: Ticker; - fiveMinutes: Signal[]; - fifteenMinutes: Signal[]; - oneHour: Signal[]; - fourHour: Signal[]; - oneDay: Signal[]; + fiveMinutes: LightSignal[]; + fifteenMinutes: LightSignal[]; + oneHour: LightSignal[]; + fourHour: LightSignal[]; + oneDay: LightSignal[]; } export interface CandlesWithIndicatorsResponse { diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx index 5963231..3060bf3 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx @@ -3,11 +3,9 @@ import React, {useState} from 'react' import 'react-toastify/dist/ReactToastify.css' import {Tabs} from '../../components/mollecules' import BacktestScanner from './backtestScanner' -import BacktestUpload from './backtestUpload' -import BacktestGenetic from './backtestGenetic' import BacktestGeneticBundle from './backtestGeneticBundle' -import BacktestBundleForm from './backtestBundleForm'; import type {TabsType} from '../../global/type.tsx' +import BacktestBundleForm from './backtestBundleForm' // Tabs Array const tabs: TabsType = [ @@ -21,19 +19,9 @@ const tabs: TabsType = [ index: 1, label: 'Scanner', }, - { - Component: BacktestUpload, - index: 2, - label: 'Upload', - }, - { - Component: BacktestGenetic, - index: 3, - label: 'Genetic', - }, { Component: BacktestGeneticBundle, - index: 4, + index: 2, label: 'GeneticBundle', }, ] diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestBundleForm.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestBundleForm.tsx index 52a71ad..3fd1830 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtestBundleForm.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestBundleForm.tsx @@ -1,5 +1,5 @@ import React, {useState} from 'react'; -import {BacktestClient} from '../../generated/ManagingApi'; +import {AccountClient, BacktestClient} from '../../generated/ManagingApi'; import type { MoneyManagementRequest, RunBacktestRequest, @@ -12,6 +12,7 @@ import {useCustomScenario} from '../../app/store/customScenario'; import useApiUrlStore from '../../app/store/apiStore'; import Toast from '../../components/mollecules/Toast/Toast'; import BundleRequestsTable from './bundleRequestsTable'; +import {useQuery} from '@tanstack/react-query'; // Placeholder types (replace with your actual types) type Indicator = { name: string; params?: Record }; @@ -52,6 +53,22 @@ const timeframeMap: Record = { const BacktestBundleForm: React.FC = () => { const {apiUrl} = useApiUrlStore() + + // API clients + const accountClient = new AccountClient({}, apiUrl); + + // Data fetching + const { data: accounts, isSuccess } = useQuery({ + queryFn: async () => { + const fetchedAccounts = await accountClient.account_GetAccounts(); + if (fetchedAccounts.length > 0 && accountName === 'default') { + setAccountName(fetchedAccounts[0].name); + } + return fetchedAccounts; + }, + queryKey: ['accounts'], + }); + // Form state const [strategyName, setStrategyName] = useState(''); const [loopback, setLoopback] = useState(14); @@ -71,6 +88,7 @@ const BacktestBundleForm: React.FC = () => { const [flipOnlyInProfit, setFlipOnlyInProfit] = useState(false); const [closeEarly, setCloseEarly] = useState(false); const [startingCapital, setStartingCapital] = useState(10000); + const [accountName, setAccountName] = useState(accounts?.[0]?.name ?? ''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); @@ -99,7 +117,7 @@ const BacktestBundleForm: React.FC = () => { timeframe: timeframeMap[timeframe], }; const config: TradingBotConfigRequest = { - accountName: 'default', // TODO: let user pick + accountName: accountName, ticker: tickerMap[asset], timeframe: timeframeMap[timeframe], isForWatchingOnly: false, @@ -170,6 +188,22 @@ const BacktestBundleForm: React.FC = () => { />
+ {/* Select account */} +
+ + +
+ {/* Scenario/Indicators section */}
{ ); }; -export default BacktestBundleForm; \ No newline at end of file +export default BacktestBundleForm; \ No newline at end of file diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx index 60d5f4a..e8a5fbb 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx @@ -5,21 +5,22 @@ import {useForm} from 'react-hook-form' import useApiUrlStore from '../../app/store/apiStore' import useBacktestStore from '../../app/store/backtestStore' import { - AccountClient, - type Backtest, - BacktestClient, - DataClient, - type IndicatorRequest, - IndicatorType, - MoneyManagementClient, - type MoneyManagementRequest, - type RunBacktestRequest, - ScenarioClient, - type ScenarioRequest, - SignalType, - Ticker, - Timeframe, - type TradingBotConfigRequest, + AccountClient, + type Backtest, + BacktestClient, + DataClient, + type IndicatorRequest, + IndicatorType, + LightBacktestResponse, + MoneyManagementClient, + type MoneyManagementRequest, + type RunBacktestRequest, + ScenarioClient, + type ScenarioRequest, + SignalType, + Ticker, + Timeframe, + type TradingBotConfigRequest, } from '../../generated/ManagingApi' import {Toast} from '../../components/mollecules' import BacktestTable from '../../components/organism/Backtest/backtestTable' @@ -2057,7 +2058,7 @@ const BacktestGenetic: React.FC = () => {

Best Results

- r.backtest).filter(Boolean) as Backtest[]} displaySummary={false} /> + r.backtest).filter(Boolean) as LightBacktestResponse[]} displaySummary={false} />
diff --git a/src/Managing.WebApp/src/pages/botsPage/botList.tsx b/src/Managing.WebApp/src/pages/botsPage/botList.tsx index 8b853a0..0e5bcbc 100644 --- a/src/Managing.WebApp/src/pages/botsPage/botList.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/botList.tsx @@ -7,12 +7,12 @@ import ManualPositionModal from '../../components/mollecules/ManualPositionModal import TradesModal from '../../components/mollecules/TradesModal/TradesModal' import {TradeChart, UnifiedTradingModal} from '../../components/organism' import { - BotClient, - BotType, - MoneyManagement, - Position, - TradingBotResponse, - UserClient + BotClient, + BotType, + MoneyManagement, + Position, + TradingBotResponse, + UserClient } from '../../generated/ManagingApi' import type {IBotList} from '../../global/type.tsx' import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal' @@ -300,14 +300,14 @@ const BotList: React.FC = ({ list }) => { { - const realized = p.profitAndLoss?.realized ?? 0 + const realized = p.ProfitAndLoss?.realized ?? 0 return realized > 0 ? p : null })} > { - const realized = p.profitAndLoss?.realized ?? 0 + const realized = p.ProfitAndLoss?.realized ?? 0 return realized <= 0 ? p : null })} >