Filter everything with users (#16)

* Filter everything with users

* Fix backtests and user management

* Add cursor rules

* Fix backtest and bots

* Update configs names

* Sign until unauth

* Setup delegate

* Setup delegate and sign

* refact

* Enhance Privy signature generation with improved cryptographic methods

* Add Fastify backend

* Add Fastify backend routes for privy

* fix privy signing

* fix privy client

* Fix tests

* add gmx core

* fix merging sdk

* Fix tests

* add gmx core

* add gmx core

* add privy to boilerplate

* clean

* fix

* add fastify

* Remove Managing.Fastify submodule

* Add Managing.Fastify as regular directory instead of submodule

* Update .gitignore to exclude Managing.Fastify dist and node_modules directories

* Add token approval functionality to Privy plugin

- Introduced a new endpoint `/approve-token` for approving ERC20 tokens.
- Added `approveToken` method to the Privy plugin for handling token approvals.
- Updated `signPrivyMessage` to differentiate between message signing and token approval requests.
- Enhanced the plugin with additional schemas for input validation.
- Included new utility functions for token data retrieval and message construction.
- Updated tests to verify the new functionality and ensure proper request decoration.

* Add PrivyApproveTokenResponse model for token approval response

- Created a new class `PrivyApproveTokenResponse` to encapsulate the response structure for token approval requests.
- The class includes properties for `Success` status and a transaction `Hash`.

* Refactor trading commands and enhance API routes

- Updated `OpenPositionCommandHandler` to use asynchronous methods for opening trades and canceling orders.
- Introduced new Fastify routes for opening positions and canceling orders with appropriate request validation.
- Modified `EvmManager` to handle both Privy and non-Privy wallet operations, utilizing the Fastify API for Privy wallets.
- Adjusted test configurations to reflect changes in account types and added helper methods for testing Web3 proxy services.

* Enhance GMX trading functionality and update dependencies

- Updated `dev:start` script in `package.json` to include the `-d` flag for Fastify.
- Upgraded `fastify-cli` dependency to version 7.3.0.
- Added `sourceMap` option to `tsconfig.json`.
- Refactored GMX plugin to improve position opening logic, including enhanced error handling and validation.
- Introduced a new method `getMarketInfoFromTicker` for better market data retrieval.
- Updated account type in `PrivateKeys.cs` to use `Privy`.
- Adjusted `EvmManager` to utilize the `direction` enum directly for trade direction handling.

* Refactor GMX plugin for improved trading logic and market data retrieval

- Enhanced the `openGmxPositionImpl` function to utilize the `TradeDirection` enum for trade direction handling.
- Introduced `getTokenDataFromTicker` and `getMarketByIndexToken` functions for better market and token data retrieval.
- Updated collateral calculation and logging for clarity.
- Adjusted `EvmManager` to ensure proper handling of price values in trade requests.

* Refactor GMX plugin and enhance testing for position opening

- Updated `test:single` script in `package.json` to include TypeScript compilation before running tests.
- Removed `this` context from `getClientForAddress` function and replaced logging with `console.error`.
- Improved collateral calculation in `openGmxPositionImpl` for better precision.
- Adjusted type casting for `direction` in the API route to utilize `TradeDirection` enum.
- Added a new test for opening a long position in GMX, ensuring functionality and correctness.

* Update sdk

* Update

* update fastify

* Refactor start script in package.json to simplify command execution

- Removed the build step from the start script, allowing for a more direct launch of the Fastify server.

* Update package.json for Web3Proxy

- Changed the name from "Web3Proxy" to "web3-proxy".
- Updated version from "0.0.0" to "1.0.0".
- Modified the description to "The official Managing Web3 Proxy".

* Update Dockerfile for Web3Proxy

- Upgraded Node.js base image from 18-alpine to 22.14.0-alpine.
- Added NODE_ENV environment variable set to production.

* Refactor Dockerfile and package.json for Web3Proxy

- Removed the build step from the Dockerfile to streamline the image creation process.
- Updated the start script in package.json to include the build step, ensuring the application is built before starting the server.

* Add fastify-tsconfig as a development dependency in Dockerfile-web3proxy

* Remove fastify-tsconfig extension from tsconfig.json for Web3Proxy

* Add PrivyInitAddressResponse model for handling initialization responses

- Introduced a new class `PrivyInitAddressResponse` to encapsulate the response structure for Privy initialization, including properties for success status, USDC hash, order vault hash, and error message.

* Update

* Update

* Remove fastify-tsconfig installation from Dockerfile-web3proxy

* Add build step to Dockerfile-web3proxy

- Included `npm run build` in the Dockerfile to ensure the application is built during the image creation process.

* Update

* approvals

* Open position from front embedded wallet

* Open position from front embedded wallet

* Open position from front embedded wallet

* Fix call contracts

* Fix limit price

* Close position

* Fix close position

* Fix close position

* add pinky

* Refactor position handling logic

* Update Dockerfile-pinky to copy package.json and source code from the correct directory

* Implement password protection modal and enhance UI with new styles; remove unused audio elements and update package dependencies.

* add cancel orders

* Update callContract function to explicitly cast account address as Address type

* Update callContract function to cast transaction parameters as any type for compatibility

* Cast transaction parameters as any type in approveTokenImpl for compatibility

* Cast wallet address and transaction parameters as Address type in approveTokenImpl for type safety

* Add .env configuration file for production setup including database and server settings

* Refactor home route to update welcome message and remove unused SDK configuration code

* add referral code

* fix referral

* Add sltp

* Fix typo

* Fix typo

* setup sltp on backtend

* get orders

* get positions with slp

* fixes

* fixes close position

* fixes

* Remove MongoDB project references from Dockerfiles for managing and worker APIs

* Comment out BotManagerWorker service registration and remove MongoDB project reference from Dockerfile

* fixes
This commit is contained in:
Oda
2025-04-20 22:18:27 +07:00
committed by GitHub
parent 0ae96a3278
commit 528c62a0a1
400 changed files with 94446 additions and 1635 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,72 +0,0 @@
---
description: Guideline for React Vite app frontend
globs:
alwaysApply: false
---
You are an expert in Solidity, TypeScript, Node.js, Next.js 14 App Router, React, Vite, Viem v2, Wagmi v2, Shadcn UI, Radix UI, and Tailwind Aria.
Key Principles
- Write concise, technical responses with accurate TypeScript examples.
- Use functional, declarative programming. Avoid classes.
- Prefer iteration and modularization over duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading).
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
- Favor named exports for components.
- Use the Receive an Object, Return an Object (RORO) pattern.
JavaScript/TypeScript
- Use "function" keyword for pure functions. Omit semicolons.
- Use TypeScript for all code. Prefer interfaces over types. Avoid enums, use maps.
- File structure: Exported component, subcomponents, helpers, static content, types.
- Avoid unnecessary curly braces in conditional statements.
- For single-line statements in conditionals, omit curly braces.
- Use concise, one-line syntax for simple conditional statements (e.g., if (condition) doSomething()).
Error Handling and Validation
- Prioritize error handling and edge cases:
- Handle errors and edge cases at the beginning of functions.
- Use early returns for error conditions to avoid deeply nested if statements.
- Place the happy path last in the function for improved readability.
- Avoid unnecessary else statements; use if-return pattern instead.
- Use guard clauses to handle preconditions and invalid states early.
- Implement proper error logging and user-friendly error messages.
- Consider using custom error types or error factories for consistent error handling.
React/Next.js
- Use functional components and TypeScript interfaces.
- Use declarative JSX.
- Use function, not const, for components.
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling.
- Implement responsive design with Tailwind CSS.
- Use mobile-first approach for responsive design.
- Place static content and interfaces at file end.
- Use content variables for static content outside render functions.
- Minimize 'use client', 'useEffect', and 'setState'. Favor RSC.
- Use Zod for form validation.
- Wrap client components in Suspense with fallback.
- Use dynamic loading for non-critical components.
- Optimize images: WebP format, size data, lazy loading.
- Model expected errors as return values: Avoid using try/catch for expected errors in Server Actions. Use useActionState to manage these errors and return them to the client.
- Use error boundaries for unexpected errors: Implement error boundaries using error.tsx and global-error.tsx files to handle unexpected errors and provide a fallback UI.
- Use useActionState with react-hook-form for form validation.
- Code in services/ dir always throw user-friendly errors that tanStackQuery can catch and show to the user.
- Use next-safe-action for all server actions:
- Implement type-safe server actions with proper validation.
- Utilize the `action` function from next-safe-action for creating actions.
- Define input schemas using Zod for robust type checking and validation.
- Handle errors gracefully and return appropriate responses.
- Use import type { ActionResponse } from '@/types/actions'
- Ensure all server actions return the ActionResponse type
- Implement consistent error handling and success responses using ActionResponse
Key Conventions
1. Rely on Next.js App Router for state changes.
2. Prioritize Web Vitals (LCP, CLS, FID).
3. Minimize 'use client' usage:
- Prefer server components and Next.js SSR features.
- Use 'use client' only for Web API access in small components.
- Avoid using 'use client' for data fetching or state management.
Refer to Next.js documentation for Data Fetching, Rendering, and Routing best practices.

View File

@@ -1,10 +1,10 @@
--- ---
description: Guideline for .NET C# backend description: Guideline for .NET C# backend
globs: globs:
alwaysApply: false alwaysApply: true
--- ---
# .NET Development Rules for Quantitative Finance # .NET React Typescript Rules for Quantitative Finance
You are a senior .NET backend developer and experimental quant with deep expertise in financial mathematics, algorithmic trading, and market indicators. You are a senior .NET backend developer and experimental quant with deep expertise in financial mathematics, algorithmic trading, and market indicators.
@@ -15,6 +15,15 @@ You are a senior .NET backend developer and experimental quant with deep experti
- Validate models with historical backtesting frameworks - Validate models with historical backtesting frameworks
- Maintain audit trails for financial calculations - Maintain audit trails for financial calculations
Key Principles
- Write concise, technical responses with accurate TypeScript examples.
- Use functional, declarative programming. Avoid classes.
- Prefer iteration and modularization over duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading).
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
- Favor named exports for components.
- Use the Receive an Object, Return an Object (RORO) pattern.
## Code Style and Structure ## Code Style and Structure
- Write concise, idiomatic C# code with accurate examples. - Write concise, idiomatic C# code with accurate examples.
- Follow .NET and ASP.NET Core conventions and best practices. - Follow .NET and ASP.NET Core conventions and best practices.
@@ -32,7 +41,7 @@ You are a senior .NET backend developer and experimental quant with deep experti
## C# and .NET Usage ## C# and .NET Usage
- Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment). - Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment).
- Leverage built-in ASP.NET Core features and middleware. - Leverage built-in ASP.NET Core features and middleware.
- Use Entity Framework Core effectively for database operations. - Use MongoDb and Influxdb effectively for database operations.
## Syntax and Formatting ## Syntax and Formatting
- Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) - Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
@@ -58,25 +67,43 @@ You are a senior .NET backend developer and experimental quant with deep experti
- Use efficient LINQ queries and avoid N+1 query problems. - Use efficient LINQ queries and avoid N+1 query problems.
- Implement pagination for large data sets. - Implement pagination for large data sets.
## Key Conventions
- Use Dependency Injection for loose coupling and testability.
- Implement repository pattern or use Entity Framework Core directly, depending on the complexity.
- Use AutoMapper for object-to-object mapping if needed.
- Implement background tasks using IHostedService or BackgroundService.
## Testing ## Testing
- Write unit tests using xUnit, NUnit, or MSTest. - Write unit tests using xUnit.
- Use Moq or NSubstitute for mocking dependencies. - Use Mock or NSubstitute for mocking dependencies.
- Implement integration tests for API endpoints. - Implement integration tests for API endpoints.
## Security ## Security
- Use Authentication and Authorization middleware. - Give me advice when you see that some data should be carefully handled
- Implement JWT authentication for stateless API authentication.
- Use HTTPS and enforce SSL.
- Implement proper CORS policies.
## API Documentation ## API Documentation
- Use Swagger/OpenAPI for API documentation (as per installed Swashbuckle.AspNetCore package). - Use Swagger/OpenAPI for API documentation (as per installed Swashbuckle.AspNetCore package).
- Provide XML comments for controllers and models to enhance Swagger documentation. - Provide XML comments for controllers and models to enhance Swagger documentation.
React/Tailwind/DaisyUI
- Use functional components and TypeScript interfaces.
- Use declarative JSX.
- Use function, not const, for components.
- Use DaisyUI Tailwind Aria for components and styling.
- Implement responsive design with Tailwind CSS.
- Use mobile-first approach for responsive design.
- Place static content and interfaces at file end.
- Use content variables for static content outside render functions.
- Minimize 'use client', 'useEffect', and 'setState'. Favor RSC.
- Use Zod for form validation.
- Wrap client components in Suspense with fallback.
- Use dynamic loading for non-critical components.
- Optimize images: WebP format, size data, lazy loading.
- Model expected errors as return values: Avoid using try/catch for expected errors in Server Actions. Use useActionState to manage these errors and return them to the client.
- Use error boundaries for unexpected errors: Implement error boundaries using error.tsx and global-error.tsx files to handle unexpected errors and provide a fallback UI.
- Use useActionState with react-hook-form for form validation.
- Code in services/ dir always throw user-friendly errors that tanStackQuery can catch and show to the user
## Do not forget
- Always implement the method that you created
- Before creating new object or new method/function check if there a code that can be called
- Most the time you will need to update multiple layer of code files. Make sure to reference all the method that you created when required
- When you think its necessary update all the code from the database to the front end
- Do not update ManagingApi.ts, user will always do it with nswag
- Do not reference new react library if a component already exist in mollecules or atoms
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components. Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components.

18
.gitignore vendored
View File

@@ -362,4 +362,20 @@ src/Managing.Api.Workers/appsettings.Oda-Sandbox.json
/src/Managing.WebApp/package-lock.json /src/Managing.WebApp/package-lock.json
src/Managing.Infrastructure.Tests/PrivateKeys.cs src/Managing.Infrastructure.Tests/PrivateKeys.cs
/src/Managing.Infrastructure.Tests/PrivateKeys.cs /src/Managing.Infrastructure.Tests/PrivateKeys.cs
src/Managing.Infrastructure.Tests/PrivateKeys.cs
# Managing.Web3Proxy build folders
/src/Managing.Web3Proxy/build/
/src/Managing.Web3Proxy/build-*/
/src/Managing.Web3Proxy/dist/
/src/Managing.Web3Proxy/node_modules/
/src/Managing.Web3Proxy/.turbo/
/src/Managing.Web3Proxy/coverage/
/src/Managing.Web3Proxy/.env
/src/Managing.Web3Proxy/.env.*
/src/Managing.Web3Proxy2/node_modules/
/src/Managing.Web3Proxy2/dist/
/src/Managing.Fastify/dist/
/src/Managing.Fastify/node_modules/
# Node.js Tools for Visual Studio
node_modules/

View File

@@ -90,6 +90,20 @@ It contains bot management, backtesting, scenario management and money managemen
# Features # Features
## Privy
Front-end required:
- Sign message to get jwt
- Delegate embedded address
- Sign delegation
- Send >10 USDc and 5$ of ETH for txn fees
- Trigger to init address
Backend actions:
- Approve GMX contracts addresses
- Approve USDc contract address
## Money Management ## Money Management
- Create a defined money management for a given timeframe (StopLoss, TakeProfit, Amount to risk) - Create a defined money management for a given timeframe (StopLoss, TakeProfit, Amount to risk)
@@ -201,3 +215,30 @@ function MyComponent() {
For more information, see the [Privy documentation](https://docs.privy.io/). For more information, see the [Privy documentation](https://docs.privy.io/).
# Development
## Utilities
The project includes several utility scripts to help with development:
### ESM Import Fixer
When working with ES Modules in Node.js:
- Local module imports require `.js` file extensions
- JSON imports require proper type assertions
We provide utility scripts that automatically handle these requirements:
```bash
# Fix imports in the src directory (add .js extensions and JSON assertions)
npm run fix-imports
# Update JSON imports to use modern "with { type: "json" }" syntax
npm run update-json-imports
# Run both scripts in sequence (fix imports then update JSON import syntax)
npm run prepare-code
```
For more details, see the [scripts documentation](scripts/README.md).

4
definition-pinky Normal file
View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./src/Managing.Pinky/Dockerfile-pinky"
}

4
definition-proxy-api Normal file
View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./src/Managing.Web3Proxy/Dockerfile-web3proxy"
}

191
scripts/README.md Normal file
View File

@@ -0,0 +1,191 @@
# Scripts
This directory contains utility scripts for the project.
## Add JS Extensions Script
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.
### 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:
```bash
# Fix imports in the src directory (default)
npm run fix-imports
# Fix imports in a specific directory
npm run fix-imports-dir -- path/to/directory
```
Or run the script directly:
```bash
# Fix imports in the src directory (default)
node scripts/add-js-extensions.mjs
# Fix imports in a specific directory
node scripts/add-js-extensions.mjs path/to/directory
```
### What This Script Does
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
### Examples
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

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const stat = promisify(fs.stat);
// Regex to match import statements with relative paths without extensions
const importRegex = /import\s+(?:(?:[\w*\s{},]*)\s+from\s+)?['"](\.[^'"]*)['"]/g;
async function processFile(filePath) {
try {
// Read file content
const content = await readFile(filePath, 'utf8');
// Skip files that are already processed or don't need processing
if (!content.match(importRegex)) {
return { filePath, changed: false };
}
// Replace imports without extensions
let modifiedContent = content.replace(importRegex, (match, importPath) => {
// Skip if already has an extension
if (path.extname(importPath)) {
return match;
}
// Add .js extension
return match.replace(importPath, `${importPath}.js`);
});
// Only write if content changed
if (content !== modifiedContent) {
await writeFile(filePath, modifiedContent, 'utf8');
return { filePath, changed: true };
}
return { filePath, changed: false };
} catch (error) {
return { filePath, error: error.message };
}
}
async function walkDir(dir, fileTypes = ['.ts', '.tsx', '.js', '.jsx']) {
const entries = await readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and .git directories
if (entry.name !== 'node_modules' && entry.name !== '.git') {
files.push(...await walkDir(fullPath, fileTypes));
}
} else if (entry.isFile()) {
const ext = path.extname(entry.name);
if (fileTypes.includes(ext)) {
files.push(fullPath);
}
}
}
return files;
}
async function main() {
const rootDir = process.argv[2] || path.resolve(process.cwd(), 'src');
console.log(`Scanning directory: ${rootDir}`);
try {
const files = await walkDir(rootDir);
console.log(`Found ${files.length} files to process`);
const results = await Promise.all(files.map(processFile));
// Collect statistics
const changed = results.filter(r => r.changed);
const errors = results.filter(r => r.error);
console.log('\nSummary:');
console.log(`- Total files scanned: ${files.length}`);
console.log(`- Files modified: ${changed.length}`);
console.log(`- Errors encountered: ${errors.length}`);
if (changed.length > 0) {
console.log('\nModified files:');
changed.forEach(({ filePath }) => {
console.log(`- ${path.relative(process.cwd(), filePath)}`);
});
}
if (errors.length > 0) {
console.log('\nErrors:');
errors.forEach(({ filePath, error }) => {
console.log(`- ${path.relative(process.cwd(), filePath)}: ${error}`);
});
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main().catch(console.error);

130
scripts/add-js-extensions.mjs Executable file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env node
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
// Get the current file path and directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gmxsdkDir = path.resolve(__dirname, '../src/Managing.Fastify/src/generated/gmxsdk');
// Regex to match import statements with relative paths without extensions
// This regex captures: import statements, export statements, and dynamic imports
const importRegex = /((?:import|export)(?:\s+(?:[\w*\s{},]*)\s+from\s+)?['"]|import\(['"])(\.[^'")\s]*)(['"]|\))/g;
// Regex to match JSON imports that don't have the assert { type: 'json' }
const jsonImportRegex = /((?:import|export)(?:\s+(?:[\w*\s{},]*)\s+from\s+)?['"]|import\(['"])(\.[^'")\s]*\.json)(['"])(?!\s+assert\s*{\s*type\s*:\s*['"]json['"]\s*})/g;
async function processFile(filePath) {
try {
// Read file content
const content = await fs.readFile(filePath, 'utf8');
// Skip files that are already processed or don't need processing
if (!content.match(importRegex) && !content.match(jsonImportRegex)) {
return { filePath, changed: false };
}
// First: Handle normal imports - add .js extensions
let modifiedContent = content.replace(importRegex, (match, prefix, importPath, suffix) => {
// Skip if already has an extension
if (path.extname(importPath)) {
return match;
}
// Add .js extension
return `${prefix}${importPath}.js${suffix}`;
});
// Second: Handle JSON imports - add assert { type: 'json' }
modifiedContent = modifiedContent.replace(jsonImportRegex, (match, prefix, importPath, suffix) => {
// Add the JSON assertion
return `${prefix}${importPath}${suffix} assert { type: 'json' }`;
});
// Only write if content changed
if (content !== modifiedContent) {
await fs.writeFile(filePath, modifiedContent, 'utf8');
return { filePath, changed: true };
}
return { filePath, changed: false };
} catch (error) {
return { filePath, error: error.message };
}
}
async function walkDir(dir, fileTypes = ['.ts', '.tsx', '.js', '.jsx', '.mjs']) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules, .git, build, and dist directories
const skipDirs = ['node_modules', '.git', 'build', 'dist', '.next'];
if (!skipDirs.includes(entry.name)) {
files.push(...await walkDir(fullPath, fileTypes));
}
} else if (entry.isFile()) {
const ext = path.extname(entry.name);
if (fileTypes.includes(ext)) {
files.push(fullPath);
}
}
}
return files;
}
async function main() {
console.log(`Scanning @gmxsdk directory: ${gmxsdkDir}`);
try {
const files = await walkDir(gmxsdkDir);
console.log(`Found ${files.length} files to process in @gmxsdk`);
// Process files in chunks to avoid memory issues with large projects
const chunkSize = 100;
const results = [];
for (let i = 0; i < files.length; i += chunkSize) {
const chunk = files.slice(i, i + chunkSize);
const chunkResults = await Promise.all(chunk.map(processFile));
results.push(...chunkResults);
// Progress update
console.log(`Processed ${Math.min(i + chunkSize, files.length)}/${files.length} files...`);
}
// Collect statistics
const changed = results.filter(r => r.changed);
const errors = results.filter(r => r.error);
console.log('\nSummary:');
console.log(`- Total files scanned: ${files.length}`);
console.log(`- Files modified: ${changed.length}`);
console.log(`- Errors encountered: ${errors.length}`);
if (changed.length > 0) {
console.log('\nModified files:');
changed.forEach(({ filePath }) => {
console.log(`- ${path.relative(process.cwd(), filePath)}`);
});
}
if (errors.length > 0) {
console.log('\nErrors:');
errors.forEach(({ filePath, error }) => {
console.log(`- ${path.relative(process.cwd(), filePath)}: ${error}`);
});
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main().catch(console.error);

487
scripts/package-lock.json generated Normal file
View File

@@ -0,0 +1,487 @@
{
"name": "managing-scripts",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "managing-scripts",
"version": "1.0.0",
"dependencies": {
"glob": "^10.3.10"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
}
}
}

9
scripts/package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "managing-scripts",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"glob": "^10.3.10"
}
}

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, '..');
// Regex to match JSON imports that have the assert { type: 'json' }
const jsonImportWithAssertRegex = /^(import\s+.+?\s+from\s+['"].*?\.json['"])\s+assert\s+\{\s*type\s*:\s*['"]json['"]\s*\}\s*;/gm;
async function processFile(filePath) {
try {
// Read the file content
const content = await fs.readFile(filePath, 'utf8');
// Check if the file contains JSON imports with assertions
if (!jsonImportWithAssertRegex.test(content)) {
return false; // No changes needed
}
// Reset regex lastIndex
jsonImportWithAssertRegex.lastIndex = 0;
// Replace the assertions in JSON imports
const updatedContent = content.replace(jsonImportWithAssertRegex, '$1;');
// Write the updated content back to the file
await fs.writeFile(filePath, updatedContent, 'utf8');
return true; // File was updated
} catch (error) {
console.error(`Error processing file ${filePath}:`, error);
return false;
}
}
async function main() {
try {
console.log('Searching for TypeScript and JavaScript files...');
// Find all TS and JS files in the project
const files = await glob('**/*.{ts,js,mts,mjs}', {
cwd: rootDir,
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/build-test/**']
});
console.log(`Found ${files.length} TypeScript/JavaScript files.`);
let updatedFiles = 0;
// Process files in parallel
const results = await Promise.all(
files.map(file => processFile(path.join(rootDir, file)))
);
updatedFiles = results.filter(Boolean).length;
console.log(`Removed JSON assertions from ${updatedFiles} files.`);
if (updatedFiles > 0) {
console.log('Successfully removed all JSON import assertions!');
} else {
console.log('No files needed to be updated.');
}
} catch (error) {
console.error('Error processing files:', error);
process.exit(1);
}
}
main();

124
scripts/update-json-imports.mjs Executable file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env node
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, '..');
const gmxsdkDir = path.resolve(rootDir, 'src/Managing.Fastify/src/generated/gmxsdk');
// Regex to match JSON imports with assert { type: 'json' } pattern
const oldJsonImportRegex = /^(import\s+.+?\s+from\s+['"].*?\.json['"])\s+assert\s+\{\s*type\s*:\s*['"]json['"]\s*\}\s*;/gm;
// Regex to match JSON imports without any type assertion
const plainJsonImportRegex = /^(import\s+.+?\s+from\s+['"].*?\.json['"])\s*;/gm;
// Regex to match JSON imports with both assert and with patterns (erroneous state)
const doublePatternJsonImportRegex = /^(import\s+.+?\s+from\s+['"].*?\.json['"])\s+assert\s+\{\s*type\s*:\s*['"]json['"]\s*\}\s+with\s+\{\s*type\s*:\s*["']json["']\s*\}\s*;/gm;
// Regex to match JSON imports that already have the with pattern
const withJsonImportRegex = /^(import\s+.+?\s+from\s+['"].*?\.json['"])\s+with\s+\{\s*type\s*:\s*["']json["']\s*\}\s*;/gm;
async function processFile(filePath) {
try {
// Read the file content
const content = await fs.readFile(filePath, 'utf8');
// Check for all import patterns
const hasDoublePattern = doublePatternJsonImportRegex.test(content);
doublePatternJsonImportRegex.lastIndex = 0;
const hasOldJsonImports = oldJsonImportRegex.test(content);
oldJsonImportRegex.lastIndex = 0;
const hasPlainJsonImports = plainJsonImportRegex.test(content);
plainJsonImportRegex.lastIndex = 0;
const hasWithPattern = withJsonImportRegex.test(content);
withJsonImportRegex.lastIndex = 0;
// If no patterns to fix, return early
if (!hasDoublePattern && !hasOldJsonImports && !hasPlainJsonImports && !hasWithPattern) {
return false; // No changes needed
}
// Apply fixes in sequence
let updatedContent = content;
// 1. First fix the double pattern errors (both assert and with)
if (hasDoublePattern) {
updatedContent = updatedContent.replace(doublePatternJsonImportRegex, '$1 with { type: "json" };');
}
// 2. Replace assert pattern with with pattern
if (hasOldJsonImports) {
updatedContent = updatedContent.replace(oldJsonImportRegex, '$1 with { type: "json" };');
}
// 3. Add with pattern to plain JSON imports
if (hasPlainJsonImports) {
// Process line by line to avoid matching imports that already have with
const lines = updatedContent.split('\n');
const modifiedLines = lines.map(line => {
// Check if line is a plain JSON import without assert/with patterns
if (plainJsonImportRegex.test(line) && !line.includes('assert') && !line.includes('with')) {
plainJsonImportRegex.lastIndex = 0;
return line.replace(plainJsonImportRegex, '$1 with { type: "json" };');
}
return line;
});
updatedContent = modifiedLines.join('\n');
}
// If content changed, write the updated content back to the file
if (updatedContent !== content) {
await fs.writeFile(filePath, updatedContent, 'utf8');
return true; // File was updated
}
return false; // No changes made
} catch (error) {
console.error(`Error processing file ${filePath}:`, error);
return false;
}
}
async function main() {
try {
console.log('Searching for TypeScript and JavaScript files in @gmxsdk...');
// Find all TS and JS files in the gmxsdk directory
const files = await glob('**/*.{ts,js,mts,mjs}', {
cwd: gmxsdkDir,
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/build-test/**']
});
console.log(`Found ${files.length} TypeScript/JavaScript files in @gmxsdk.`);
let updatedFiles = 0;
// Process files in parallel
const results = await Promise.all(
files.map(file => processFile(path.join(gmxsdkDir, file)))
);
updatedFiles = results.filter(Boolean).length;
console.log(`Updated JSON imports in ${updatedFiles} files.`);
if (updatedFiles > 0) {
console.log('Successfully updated all JSON import statements to use "with { type: "json" }" syntax!');
} else {
console.log('No files needed to be updated.');
}
} catch (error) {
console.error('Error processing files:', error);
process.exit(1);
}
}
main();

BIN
src/.DS_Store vendored

Binary file not shown.

View File

@@ -11,7 +11,6 @@ COPY ["/src/Managing.Api/Managing.Api.csproj", "Managing.Api/"]
COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"] COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"] COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"] COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["/src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["/src/Managing.Common/Managing.Common.csproj", "Managing.Common/"] COPY ["/src/Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["/src/Managing.Core/Managing.Core.csproj", "Managing.Core/"] COPY ["/src/Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"] COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]

View File

@@ -11,7 +11,6 @@ COPY ["/src/Managing.Api.Workers/Managing.Api.Workers.csproj", "Managing.Api.Wor
COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"] COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"] COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"] COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["/src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["/src/Managing.Common/Managing.Common.csproj", "Managing.Common/"] COPY ["/src/Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["/src/Managing.Core/Managing.Core.csproj", "Managing.Core/"] COPY ["/src/Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"] COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]

View File

@@ -11,7 +11,6 @@ COPY ["Managing.Api.Workers/Managing.Api.Workers.csproj", "Managing.Api.Workers/
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"] COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"] COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"] COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"] COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"] COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"] COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]

View File

@@ -19,7 +19,7 @@ namespace Managing.Api.Controllers;
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class BacktestController : ControllerBase public class BacktestController : BaseController
{ {
private readonly IHubContext<BotHub> _hubContext; private readonly IHubContext<BotHub> _hubContext;
private readonly IBacktester _backtester; private readonly IBacktester _backtester;
@@ -40,7 +40,8 @@ public class BacktestController : ControllerBase
IBacktester backtester, IBacktester backtester,
IScenarioService scenarioService, IScenarioService scenarioService,
IAccountService accountService, IAccountService accountService,
IMoneyManagementService moneyManagementService) IMoneyManagementService moneyManagementService,
IUserService userService) : base(userService)
{ {
_hubContext = hubContext; _hubContext = hubContext;
_backtester = backtester; _backtester = backtester;
@@ -50,24 +51,46 @@ public class BacktestController : ControllerBase
} }
/// <summary> /// <summary>
/// Retrieves all backtests. /// Retrieves all backtests for the authenticated user.
/// </summary> /// </summary>
/// <returns>A list of backtests.</returns> /// <returns>A list of backtests.</returns>
[HttpGet] [HttpGet]
public ActionResult<IEnumerable<Backtest>> Backtests() public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
{ {
return Ok(_backtester.GetBacktests()); var user = await GetUser();
return Ok(await _backtester.GetBacktestsByUser(user));
} }
/// <summary> /// <summary>
/// Deletes a specific backtest by ID. /// Retrieves a specific backtest by ID for the authenticated user.
/// This endpoint will also populate the candles for visualization.
/// </summary>
/// <param name="id">The ID of the backtest to retrieve.</param>
/// <returns>The requested backtest with populated candle data.</returns>
[HttpGet("{id}")]
public async Task<ActionResult<Backtest>> Backtest(string id)
{
var user = await GetUser();
var backtest = _backtester.GetBacktestByIdForUser(user, id);
if (backtest == null)
{
return NotFound($"Backtest with ID {id} not found or doesn't belong to the current user.");
}
return Ok(backtest);
}
/// <summary>
/// Deletes a specific backtest by ID for the authenticated user.
/// </summary> /// </summary>
/// <param name="id">The ID of the backtest to delete.</param> /// <param name="id">The ID of the backtest to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns> /// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete] [HttpDelete]
public ActionResult DeleteBacktest(string id) public async Task<ActionResult> DeleteBacktest(string id)
{ {
return Ok(_backtester.DeleteBacktest(id)); var user = await GetUser();
return Ok(_backtester.DeleteBacktestByUser(user, id));
} }
/// <summary> /// <summary>
@@ -90,7 +113,8 @@ public class BacktestController : ControllerBase
/// <param name="scenarioName">The name of the scenario to use for the backtest.</param> /// <param name="scenarioName">The name of the scenario to use for the backtest.</param>
/// <param name="timeframe">The timeframe for the backtest.</param> /// <param name="timeframe">The timeframe for the backtest.</param>
/// <param name="watchOnly">Whether to only watch the backtest without executing trades.</param> /// <param name="watchOnly">Whether to only watch the backtest without executing trades.</param>
/// <param name="days">The number of days to backtest.</param> /// <param name="startDate">The start date for the backtest.</param>
/// <param name="endDate">The end date for the backtest.</param>
/// <param name="balance">The starting balance for the backtest.</param> /// <param name="balance">The starting balance for the backtest.</param>
/// <param name="moneyManagementName">The name of the money management strategy to use.</param> /// <param name="moneyManagementName">The name of the money management strategy to use.</param>
/// <param name="moneyManagement">The money management strategy details, if not using a named strategy.</param> /// <param name="moneyManagement">The money management strategy details, if not using a named strategy.</param>
@@ -104,9 +128,10 @@ public class BacktestController : ControllerBase
string scenarioName, string scenarioName,
Timeframe timeframe, Timeframe timeframe,
bool watchOnly, bool watchOnly,
int days,
decimal balance, decimal balance,
string moneyManagementName, string moneyManagementName,
DateTime startDate,
DateTime endDate,
MoneyManagement? moneyManagement = null, MoneyManagement? moneyManagement = null,
bool save = false) bool save = false)
{ {
@@ -127,18 +152,14 @@ public class BacktestController : ControllerBase
nameof(moneyManagementName)); nameof(moneyManagementName));
} }
if (days > 0)
{
days = days * -1;
}
Backtest backtestResult = null; Backtest backtestResult = null;
var scenario = _scenarioService.GetScenario(scenarioName); var scenario = _scenarioService.GetScenario(scenarioName);
var account = await _accountService.GetAccount(accountName, true, false); var account = await _accountService.GetAccount(accountName, true, false);
var user = await GetUser();
if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null) if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null)
{ {
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName); moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
} }
else else
{ {
@@ -148,21 +169,37 @@ public class BacktestController : ControllerBase
if (scenario == null) if (scenario == null)
return BadRequest("No scenario found"); return BadRequest("No scenario found");
// var localCandles = FileHelpers
// .ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json")
// .TakeLast(500).ToList();
switch (botType) switch (botType)
{ {
case BotType.SimpleBot: case BotType.SimpleBot:
break; break;
case BotType.ScalpingBot: case BotType.ScalpingBot:
backtestResult = _backtester.RunScalpingBotBacktest(account, moneyManagement, ticker, scenario, backtestResult = await _backtester.RunScalpingBotBacktest(
timeframe, Convert.ToDouble(days), balance, watchOnly, save); account,
moneyManagement,
ticker,
scenario,
timeframe,
balance,
startDate,
endDate,
user,
watchOnly,
save);
break; break;
case BotType.FlippingBot: case BotType.FlippingBot:
backtestResult = _backtester.RunFlippingBotBacktest(account, moneyManagement, ticker, scenario, backtestResult = await _backtester.RunFlippingBotBacktest(
timeframe, Convert.ToDouble(days), balance, watchOnly, save); account,
moneyManagement,
ticker,
scenario,
timeframe,
balance,
startDate,
endDate,
user,
watchOnly,
save);
break; break;
} }

View File

@@ -4,11 +4,22 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs; using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands; using Managing.Application.ManageBot.Commands;
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using static Managing.Common.Enums; using static Managing.Common.Enums;
using ApplicationTradingBot = Managing.Application.Bots.TradingBot;
using ApiTradingBot = Managing.Api.Models.Responses.TradingBot;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
@@ -20,13 +31,15 @@ namespace Managing.Api.Controllers;
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class BotController : ControllerBase public class BotController : BaseController
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ILogger<BotController> _logger; private readonly ILogger<BotController> _logger;
private readonly IHubContext<BotHub> _hubContext; private readonly IHubContext<BotHub> _hubContext;
private readonly IBacktester _backtester; private readonly IBacktester _backtester;
private readonly IBotService _botService; private readonly IBotService _botService;
private readonly IAccountService _accountService;
private readonly IMoneyManagementService _moneyManagementService;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BotController"/> class. /// Initializes a new instance of the <see cref="BotController"/> class.
@@ -36,13 +49,57 @@ public class BotController : ControllerBase
/// <param name="hubContext">SignalR hub context for real-time communication.</param> /// <param name="hubContext">SignalR hub context for real-time communication.</param>
/// <param name="backtester">Backtester for running backtests on bots.</param> /// <param name="backtester">Backtester for running backtests on bots.</param>
public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext, public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext,
IBacktester backtester, IBotService botService) IBacktester backtester, IBotService botService, IUserService userService,
IAccountService accountService, IMoneyManagementService moneyManagementService) : base(userService)
{ {
_logger = logger; _logger = logger;
_mediator = mediator; _mediator = mediator;
_hubContext = hubContext; _hubContext = hubContext;
_backtester = backtester; _backtester = backtester;
_botService = botService; _botService = botService;
_accountService = accountService;
_moneyManagementService = moneyManagementService;
}
/// <summary>
/// Checks if the current authenticated user owns the account associated with the specified bot or account name
/// </summary>
/// <param name="botName">The name of the bot to check</param>
/// <param name="accountName">Optional account name to check when creating a new bot</param>
/// <returns>True if the user owns the account, False otherwise</returns>
private async Task<bool> UserOwnsBotAccount(string botName, string accountName = null)
{
try
{
var user = await GetUser();
if (user == null)
return false;
// For new bot creation, check if the user owns the account provided in the request
if (!string.IsNullOrEmpty(accountName))
{
var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
var account = await accountService.GetAccount(accountName, true, false);
// Compare the user names
return account != null && account.User != null && account.User.Name == user.Name;
}
// For existing bots, check if the user owns the bot's account
var activeBots = _botService.GetActiveBots();
var bot = activeBots.FirstOrDefault(b => b.Name == botName);
if (bot == null)
return true; // Bot doesn't exist yet, so no ownership conflict
var botAccountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
var botAccount = await botAccountService.GetAccount(bot.AccountName, true, false);
// Compare the user names
return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking if user owns bot account");
return false;
}
} }
/// <summary> /// <summary>
@@ -54,12 +111,39 @@ public class BotController : ControllerBase
[Route("Start")] [Route("Start")]
public async Task<ActionResult<string>> Start(StartBotRequest request) public async Task<ActionResult<string>> Start(StartBotRequest request)
{ {
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker, try
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, {
request.IsForWatchOnly)); // Check if user owns the account
if (!await UserOwnsBotAccount(request.BotName, request.AccountName))
{
return Forbid("You don't have permission to start this bot");
}
await NotifyBotSubscriberAsync(); // Trigger error if money management is not provided
return Ok(result); if (string.IsNullOrEmpty(request.MoneyManagementName))
{
return BadRequest("Money management name is required");
}
var user = await GetUser();
var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (moneyManagement == null)
{
return BadRequest("Money management not found");
}
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
request.IsForWatchOnly));
await NotifyBotSubscriberAsync();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting bot");
return StatusCode(500, $"Error starting bot: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -72,12 +156,26 @@ public class BotController : ControllerBase
[Route("Stop")] [Route("Stop")]
public async Task<ActionResult<string>> Stop(BotType botType, string botName) public async Task<ActionResult<string>> Stop(BotType botType, string botName)
{ {
var result = await _mediator.Send(new StopBotCommand(botType, botName)); try
_logger.LogInformation($"{botType} type called {botName} is now {result}"); {
// Check if user owns the account
if (!await UserOwnsBotAccount(botName))
{
return Forbid("You don't have permission to stop this bot");
}
await NotifyBotSubscriberAsync(); var result = await _mediator.Send(new StopBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
return Ok(result); await NotifyBotSubscriberAsync();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping bot");
return StatusCode(500, $"Error stopping bot: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -89,31 +187,68 @@ public class BotController : ControllerBase
[Route("Delete")] [Route("Delete")]
public async Task<ActionResult<bool>> Delete(string botName) public async Task<ActionResult<bool>> Delete(string botName)
{ {
var result = await _botService.DeleteBot(botName); try
await NotifyBotSubscriberAsync(); {
return Ok(result); // Check if user owns the account
if (!await UserOwnsBotAccount(botName))
{
return Forbid("You don't have permission to delete this bot");
}
var result = await _botService.DeleteBot(botName);
await NotifyBotSubscriberAsync();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting bot");
return StatusCode(500, $"Error deleting bot: {ex.Message}");
}
} }
/// <summary> /// <summary>
/// Stops all active bots. /// Stops all active bots.
/// </summary> /// </summary>
/// <returns>A string summarizing the results of the stop operations for all bots.</returns> /// <returns>A string summarizing the results of the stop operations for all bots.</returns>
[HttpGet] [HttpPost("stop-all")]
[Route("StopAll")]
public async Task<string> StopAll() public async Task<string> StopAll()
{ {
var bots = await GetBotList(); // This method should be restricted to only stop bots owned by the current user
var result = ""; var user = await GetUser();
foreach (var bot in bots) if (user == null)
return "No authenticated user found";
try
{ {
result += $"{bot.Name} : "; var bots = await GetBotList();
result += await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); // Filter to only include bots owned by the current user
result += $" |"; var userBots = new List<ApiTradingBot>();
foreach (var bot in bots)
{
var account = await _accountService.GetAccount(bot.AccountName, true, false);
// Compare the user names
if (account != null && account.User != null && account.User.Name == user.Name)
{
userBots.Add(bot);
}
}
foreach (var bot in userBots)
{
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
await _hubContext.Clients.All.SendAsync("SendNotification",
$"Bot {bot.Name} paused by {user.Name}.", "Info");
}
await NotifyBotSubscriberAsync();
return "All your bots have been stopped successfully!";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping all bots");
return $"Error stopping bots: {ex.Message}";
} }
await NotifyBotSubscriberAsync();
return result;
} }
/// <summary> /// <summary>
@@ -126,34 +261,80 @@ public class BotController : ControllerBase
[Route("Restart")] [Route("Restart")]
public async Task<ActionResult<string>> Restart(BotType botType, string botName) public async Task<ActionResult<string>> Restart(BotType botType, string botName)
{ {
var result = await _mediator.Send(new RestartBotCommand(botType, botName)); try
_logger.LogInformation($"{botType} type called {botName} is now {result}"); {
// Check if user owns the account
if (!await UserOwnsBotAccount(botName))
{
return Forbid("You don't have permission to restart this bot");
}
await NotifyBotSubscriberAsync(); var result = await _mediator.Send(new RestartBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
return Ok(result); await NotifyBotSubscriberAsync();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error restarting bot");
return StatusCode(500, $"Error restarting bot: {ex.Message}");
}
} }
/// <summary> /// <summary>
/// Restarts all active bots. /// Restarts all active bots.
/// </summary> /// </summary>
/// <returns>A string summarizing the results of the restart operations for all bots.</returns> /// <returns>A string summarizing the results of the restart operations for all bots.</returns>
[HttpGet] [HttpPost("restart-all")]
[Route("RestartAll")]
public async Task<string> RestartAll() public async Task<string> RestartAll()
{ {
var bots = await GetBotList(); var user = await GetUser();
var result = ""; if (user == null)
foreach (var bot in bots) return "No authenticated user found";
try
{ {
result += $"{bot.Name} : "; var bots = await GetBotList();
result += await _mediator.Send(new RestartBotCommand(bot.BotType, bot.Name)); // Filter to only include bots owned by the current user
result += $" |"; var userBots = new List<ApiTradingBot>();
var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
foreach (var bot in bots)
{
var account = await accountService.GetAccount(bot.AccountName, true, false);
// Compare the user names
if (account != null && account.User != null && account.User.Name == user.Name)
{
userBots.Add(bot);
}
}
foreach (var bot in userBots)
{
// We can't directly restart a bot with just BotType and Name
// Instead, stop the bot and then retrieve the backup to start it again
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
// Get the saved bot backup
var backup = _botService.GetBotBackup(bot.Name);
if (backup != null)
{
_botService.StartBotFromBackup(backup);
await _hubContext.Clients.All.SendAsync("SendNotification",
$"Bot {bot.Name} restarted by {user.Name}.", "Info");
}
}
await NotifyBotSubscriberAsync();
return "All your bots have been restarted successfully!";
}
catch (Exception e)
{
_logger.LogError(e, "Failed to restart all bots");
return $"Error restarting bots: {e.Message}";
} }
await NotifyBotSubscriberAsync();
return result;
} }
/// <summary> /// <summary>
@@ -165,12 +346,26 @@ public class BotController : ControllerBase
[Route("ToggleIsForWatching")] [Route("ToggleIsForWatching")]
public async Task<ActionResult<string>> ToggleIsForWatching(string botName) public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
{ {
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName)); try
_logger.LogInformation($"{botName} bot is now {result}"); {
// Check if user owns the account
if (!await UserOwnsBotAccount(botName))
{
return Forbid("You don't have permission to modify this bot");
}
await NotifyBotSubscriberAsync(); var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
_logger.LogInformation($"{botName} bot is now {result}");
return Ok(result); await NotifyBotSubscriberAsync();
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error toggling bot watching status");
return StatusCode(500, $"Error toggling bot watching status: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -178,7 +373,7 @@ public class BotController : ControllerBase
/// </summary> /// </summary>
/// <returns>A list of active trading bots.</returns> /// <returns>A list of active trading bots.</returns>
[HttpGet] [HttpGet]
public async Task<List<TradingBot>> GetActiveBots() public async Task<List<ApiTradingBot>> GetActiveBots()
{ {
return await GetBotList(); return await GetBotList();
} }
@@ -187,28 +382,28 @@ public class BotController : ControllerBase
/// Retrieves a list of active bots by sending a command to the mediator. /// Retrieves a list of active bots by sending a command to the mediator.
/// </summary> /// </summary>
/// <returns>A list of trading bots.</returns> /// <returns>A list of trading bots.</returns>
private async Task<List<TradingBot>> GetBotList() private async Task<List<ApiTradingBot>> GetBotList()
{ {
var result = await _mediator.Send(new GetActiveBotsCommand()); var result = await _mediator.Send(new GetActiveBotsCommand());
var list = new List<TradingBot>(); var list = new List<ApiTradingBot>();
foreach (var item in result) foreach (var item in result)
{ {
list.Add(new TradingBot list.Add(new ApiTradingBot
{ {
Status = item.GetStatus(), Status = item.GetStatus(),
Name = item.GetName(), Name = item.Name,
Candles = item.OptimizedCandles.ToList(),
Positions = item.Positions,
Signals = item.Signals.ToList(), Signals = item.Signals.ToList(),
Positions = item.Positions,
Candles = item.Candles.ToList(),
WinRate = item.GetWinRate(), WinRate = item.GetWinRate(),
ProfitAndLoss = item.GetProfitAndLoss(), ProfitAndLoss = item.GetProfitAndLoss(),
Timeframe = item.Timeframe, Timeframe = item.Timeframe,
Ticker = item.Ticker, Ticker = item.Ticker,
AccountName = item.AccountName,
Scenario = item.ScenarioName, Scenario = item.ScenarioName,
IsForWatchingOnly = item.IsForWatchingOnly, IsForWatchingOnly = item.IsForWatchingOnly,
BotType = item.BotType, BotType = item.BotType,
AccountName = item.AccountName,
MoneyManagement = item.MoneyManagement MoneyManagement = item.MoneyManagement
}); });
} }

View File

@@ -59,7 +59,7 @@ public class DataController : ControllerBase
var cacheKey = string.Concat(timeframe.ToString()); var cacheKey = string.Concat(timeframe.ToString());
var tickers = _cacheService.GetValue<List<Ticker>>(cacheKey); var tickers = _cacheService.GetValue<List<Ticker>>(cacheKey);
if (tickers == null) if (tickers == null || tickers.Count == 0)
{ {
tickers = await _exchangeService.GetTickers(timeframe); tickers = await _exchangeService.GetTickers(timeframe);
_cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2)); _cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2));

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -14,7 +15,7 @@ namespace Managing.Api.Controllers;
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class MoneyManagementController : ControllerBase public class MoneyManagementController : BaseController
{ {
private readonly IMoneyManagementService _moneyManagementService; private readonly IMoneyManagementService _moneyManagementService;
@@ -22,52 +23,60 @@ public class MoneyManagementController : ControllerBase
/// Initializes a new instance of the <see cref="MoneyManagementController"/> class. /// Initializes a new instance of the <see cref="MoneyManagementController"/> class.
/// </summary> /// </summary>
/// <param name="moneyManagementService">The service for managing money management strategies.</param> /// <param name="moneyManagementService">The service for managing money management strategies.</param>
public MoneyManagementController(IMoneyManagementService moneyManagementService) /// <param name="userService">The service for user-related operations.</param>
public MoneyManagementController(
IMoneyManagementService moneyManagementService,
IUserService userService)
: base(userService)
{ {
_moneyManagementService = moneyManagementService; _moneyManagementService = moneyManagementService;
} }
/// <summary> /// <summary>
/// Creates a new money management strategy or updates an existing one. /// Creates a new money management strategy or updates an existing one for the authenticated user.
/// </summary> /// </summary>
/// <param name="moneyManagement">The money management strategy to create or update.</param> /// <param name="moneyManagement">The money management strategy to create or update.</param>
/// <returns>The created or updated money management strategy.</returns> /// <returns>The created or updated money management strategy.</returns>
[HttpPost] [HttpPost]
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement) public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
{ {
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement)); var user = await GetUser();
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement));
} }
/// <summary> /// <summary>
/// Retrieves all money management strategies. /// Retrieves all money management strategies for the authenticated user.
/// </summary> /// </summary>
/// <returns>A list of money management strategies.</returns> /// <returns>A list of money management strategies.</returns>
[HttpGet] [HttpGet]
[Route("moneymanagements")] [Route("moneymanagements")]
public ActionResult<IEnumerable<MoneyManagement>> GetMoneyManagements() public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements()
{ {
return Ok(_moneyManagementService.GetMoneyMangements()); var user = await GetUser();
return Ok(_moneyManagementService.GetMoneyMangements(user));
} }
/// <summary> /// <summary>
/// Retrieves a specific money management strategy by name. /// Retrieves a specific money management strategy by name for the authenticated user.
/// </summary> /// </summary>
/// <param name="name">The name of the money management strategy to retrieve.</param> /// <param name="name">The name of the money management strategy to retrieve.</param>
/// <returns>The requested money management strategy if found.</returns> /// <returns>The requested money management strategy if found.</returns>
[HttpGet] [HttpGet]
public ActionResult<MoneyManagement> GetMoneyManagement(string name) public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name)
{ {
return Ok(_moneyManagementService.GetMoneyMangement(name)); var user = await GetUser();
return Ok(await _moneyManagementService.GetMoneyMangement(user, name));
} }
/// <summary> /// <summary>
/// Deletes a specific money management strategy by name. /// Deletes a specific money management strategy by name for the authenticated user.
/// </summary> /// </summary>
/// <param name="name">The name of the money management strategy to delete.</param> /// <param name="name">The name of the money management strategy to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns> /// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete] [HttpDelete]
public ActionResult DeleteMoneyManagement(string name) public async Task<ActionResult> DeleteMoneyManagement(string name)
{ {
return Ok(_moneyManagementService.DeleteMoneyManagement(name)); var user = await GetUser();
return Ok(_moneyManagementService.DeleteMoneyManagement(user, name));
} }
} }

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -16,7 +17,7 @@ namespace Managing.Api.Controllers;
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class ScenarioController : ControllerBase public class ScenarioController : BaseController
{ {
private readonly IScenarioService _scenarioService; private readonly IScenarioService _scenarioService;
@@ -24,67 +25,75 @@ public class ScenarioController : ControllerBase
/// Initializes a new instance of the <see cref="ScenarioController"/> class. /// Initializes a new instance of the <see cref="ScenarioController"/> class.
/// </summary> /// </summary>
/// <param name="scenarioService">The service for managing scenarios.</param> /// <param name="scenarioService">The service for managing scenarios.</param>
public ScenarioController(IScenarioService scenarioService) /// <param name="userService">The service for user-related operations.</param>
public ScenarioController(
IScenarioService scenarioService,
IUserService userService)
: base(userService)
{ {
_scenarioService = scenarioService; _scenarioService = scenarioService;
} }
/// <summary> /// <summary>
/// Retrieves all scenarios. /// Retrieves all scenarios for the authenticated user.
/// </summary> /// </summary>
/// <returns>A list of scenarios.</returns> /// <returns>A list of scenarios.</returns>
[HttpGet] [HttpGet]
public ActionResult<IEnumerable<Scenario>> GetScenarios() public async Task<ActionResult<IEnumerable<Scenario>>> GetScenarios()
{ {
return Ok(_scenarioService.GetScenarios()); var user = await GetUser();
return Ok(_scenarioService.GetScenariosByUser(user));
} }
/// <summary> /// <summary>
/// Creates a new scenario with the specified name and strategies. /// Creates a new scenario with the specified name and strategies for the authenticated user.
/// </summary> /// </summary>
/// <param name="name">The name of the scenario.</param> /// <param name="name">The name of the scenario.</param>
/// <param name="strategies">A list of strategy names to include in the scenario.</param> /// <param name="strategies">A list of strategy names to include in the scenario.</param>
/// <returns>The created scenario.</returns> /// <returns>The created scenario.</returns>
[HttpPost] [HttpPost]
public ActionResult<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = null) public async Task<ActionResult<Scenario>> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
{ {
return Ok(_scenarioService.CreateScenario(name, strategies, loopbackPeriod)); var user = await GetUser();
return Ok(_scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod));
} }
/// <summary> /// <summary>
/// Deletes a scenario by name. /// Deletes a scenario by name for the authenticated user.
/// </summary> /// </summary>
/// <param name="name">The name of the scenario to delete.</param> /// <param name="name">The name of the scenario to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns> /// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete] [HttpDelete]
public ActionResult DeleteScenario(string name) public async Task<ActionResult> DeleteScenario(string name)
{ {
return Ok(_scenarioService.DeleteScenario(name)); var user = await GetUser();
return Ok(_scenarioService.DeleteScenarioByUser(user, name));
} }
// Update scenario // Update scenario
[HttpPut] [HttpPut]
public ActionResult UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null) public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
{ {
return Ok(_scenarioService.UpdateScenario(name, strategies, loopbackPeriod)); var user = await GetUser();
return Ok(_scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
} }
/// <summary> /// <summary>
/// Retrieves all strategies. /// Retrieves all strategies for the authenticated user.
/// </summary> /// </summary>
/// <returns>A list of strategies.</returns> /// <returns>A list of strategies.</returns>
[HttpGet] [HttpGet]
[Route("strategy")] [Route("strategy")]
public ActionResult<IEnumerable<Strategy>> GetStrategies() public async Task<ActionResult<IEnumerable<Strategy>>> GetStrategies()
{ {
return Ok(_scenarioService.GetStrategies()); var user = await GetUser();
return Ok(_scenarioService.GetStrategiesByUser(user));
} }
/// <summary> /// <summary>
/// Creates a new strategy with specified parameters. /// Creates a new strategy with specified parameters for the authenticated user.
/// </summary> /// </summary>
/// <param name="strategyType">The type of the strategy.</param> /// <param name="strategyType">The type of the strategy.</param>
/// <param name="timeframe">The timeframe for the strategy.</param>
/// <param name="name">The name of the strategy.</param> /// <param name="name">The name of the strategy.</param>
/// <param name="period">The period for the strategy (optional).</param> /// <param name="period">The period for the strategy (optional).</param>
/// <param name="fastPeriods">The fast periods for the strategy (optional).</param> /// <param name="fastPeriods">The fast periods for the strategy (optional).</param>
@@ -97,7 +106,7 @@ public class ScenarioController : ControllerBase
/// <returns>The created strategy.</returns> /// <returns>The created strategy.</returns>
[HttpPost] [HttpPost]
[Route("strategy")] [Route("strategy")]
public ActionResult<Strategy> CreateStrategy( public async Task<ActionResult<Strategy>> CreateStrategy(
StrategyType strategyType, StrategyType strategyType,
string name, string name,
int? period = null, int? period = null,
@@ -109,7 +118,9 @@ public class ScenarioController : ControllerBase
int? smoothPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null) int? cyclePeriods = null)
{ {
return Ok(_scenarioService.CreateStrategy( var user = await GetUser();
return Ok(_scenarioService.CreateStrategyForUser(
user,
strategyType, strategyType,
name, name,
period, period,
@@ -123,21 +134,22 @@ public class ScenarioController : ControllerBase
} }
/// <summary> /// <summary>
/// Deletes a strategy by name. /// Deletes a strategy by name for the authenticated user.
/// </summary> /// </summary>
/// <param name="name">The name of the strategy to delete.</param> /// <param name="name">The name of the strategy to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns> /// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete] [HttpDelete]
[Route("strategy")] [Route("strategy")]
public ActionResult DeleteStrategy(string name) public async Task<ActionResult> DeleteStrategy(string name)
{ {
return Ok(_scenarioService.DeleteStrategy(name)); var user = await GetUser();
return Ok(_scenarioService.DeleteStrategyByUser(user, name));
} }
// Update strategy // Update strategy
[HttpPut] [HttpPut]
[Route("strategy")] [Route("strategy")]
public ActionResult UpdateStrategy( public async Task<ActionResult> UpdateStrategy(
StrategyType strategyType, StrategyType strategyType,
string name, string name,
int? period = null, int? period = null,
@@ -149,7 +161,9 @@ public class ScenarioController : ControllerBase
int? smoothPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null) int? cyclePeriods = null)
{ {
return Ok(_scenarioService.UpdateStrategy( var user = await GetUser();
return Ok(_scenarioService.UpdateStrategyByUser(
user,
strategyType, strategyType,
name, name,
period, period,

View File

@@ -1,6 +1,16 @@
using Managing.Application.Abstractions; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
@@ -8,10 +18,12 @@ namespace Managing.Api.Controllers;
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class SettingsController : ControllerBase public class SettingsController : BaseController
{ {
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
public SettingsController(ISettingsService settingsService)
public SettingsController(ISettingsService settingsService, IUserService userService)
: base(userService)
{ {
_settingsService = settingsService; _settingsService = settingsService;
} }
@@ -27,4 +39,26 @@ public class SettingsController : ControllerBase
{ {
return Ok(await _settingsService.ResetSettings()); return Ok(await _settingsService.ResetSettings());
} }
/// <summary>
/// Creates default configuration for backtesting including Money Management, Strategy, and Scenario
/// </summary>
/// <returns>A result indicating if the default configuration was created successfully</returns>
[HttpPost]
[Route("create-default-config")]
public async Task<ActionResult<bool>> CreateDefaultConfiguration()
{
try
{
var user = await GetUser();
if (user == null)
return Unauthorized("User not found");
return Ok(await _settingsService.CreateDefaultConfiguration(user));
}
catch (Exception ex)
{
return StatusCode(500, $"Error creating default configuration: {ex.Message}");
}
}
} }

View File

@@ -17,7 +17,7 @@ namespace Managing.Api.Controllers;
[ApiController] [ApiController]
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
public class TradingController : ControllerBase public class TradingController : BaseController
{ {
private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler; private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler;
private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler; private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler;
@@ -39,7 +39,8 @@ public class TradingController : ControllerBase
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler, ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler, ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
ITradingService tradingService, ITradingService tradingService,
IMediator mediator, IMoneyManagementService moneyManagementService) IMediator mediator, IMoneyManagementService moneyManagementService,
IUserService userService) : base(userService)
{ {
_logger = logger; _logger = logger;
_openTradeCommandHandler = openTradeCommandHandler; _openTradeCommandHandler = openTradeCommandHandler;
@@ -137,7 +138,8 @@ public class TradingController : ControllerBase
if (moneyManagement != null) if (moneyManagement != null)
{ {
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName); var user = await GetUser();
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
} }
var command = new OpenPositionRequest( var command = new OpenPositionRequest(

View File

@@ -11,7 +11,6 @@ COPY ["Managing.Api/Managing.Api.csproj", "Managing.Api/"]
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"] COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"] COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"] COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"] COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"] COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"] COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]

View File

@@ -9,37 +9,19 @@ namespace Managing.Api.Models.Responses
{ {
public class TradingBot public class TradingBot
{ {
[Required] [Required] public string Name { get; internal set; }
public string Name { get; internal set; } [Required] public string Status { get; internal set; }
[Required] [Required] public List<Signal> Signals { get; internal set; }
public string Status { get; internal set; } [Required] public List<Position> Positions { get; internal set; }
[Required] [Required] public List<Candle> Candles { get; internal set; }
public List<Signal> Signals { get; internal set; } [Required] public int WinRate { get; internal set; }
[Required] [Required] public decimal ProfitAndLoss { get; internal set; }
public List<Position> Positions { get; internal set; } [Required] public Timeframe Timeframe { get; internal set; }
[Required] [Required] public Ticker Ticker { get; internal set; }
public List<Candle> Candles { get; internal set; } [Required] public string Scenario { get; internal set; }
[Required] [Required] public bool IsForWatchingOnly { get; internal set; }
public RiskLevel RiskLevel { get; internal set; } [Required] public BotType BotType { get; internal set; }
[Required] [Required] public string AccountName { get; internal set; }
public int WinRate { get; internal set; } [Required] public MoneyManagement MoneyManagement { get; internal set; }
[Required]
public decimal ProfitAndLoss { get; internal set; }
[Required]
public Timeframe Timeframe { get; internal set; }
[Required]
public Ticker Ticker { get; internal set; }
[Required]
public string Scenario { get; internal set; }
[Required]
public TradingExchanges Exchange { get; internal set; }
[Required]
public bool IsForWatchingOnly { get; internal set; }
[Required]
public BotType BotType { get; internal set; }
[Required]
public string AccountName { get; internal set; }
[Required]
public MoneyManagement MoneyManagement { get; internal set; }
} }
} }

View File

@@ -12,6 +12,9 @@
"AppId": "cm6f47n1l003jx7mjwaembhup", "AppId": "cm6f47n1l003jx7mjwaembhup",
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF" "AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
}, },
"Web3Proxy": {
"BaseUrl": "http://srv-captain--web3-proxy:4111"
},
"Serilog": { "Serilog": {
"MinimumLevel": { "MinimumLevel": {
"Default": "Information", "Default": "Information",

View File

@@ -21,6 +21,9 @@
"Organization": "", "Organization": "",
"Token": "" "Token": ""
}, },
"Web3Proxy": {
"BaseUrl": "http://localhost:3000"
},
"Serilog": { "Serilog": {
"MinimumLevel": { "MinimumLevel": {
"Default": "Information", "Default": "Information",

View File

@@ -1,11 +1,13 @@
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Repositories; namespace Managing.Application.Abstractions.Repositories;
public interface IBacktestRepository public interface IBacktestRepository
{ {
void InsertBacktest(Backtest result); void InsertBacktestForUser(User user, Backtest result);
IEnumerable<Backtest> GetBacktests(); IEnumerable<Backtest> GetBacktestsByUser(User user);
void DeleteBacktestById(string id); Backtest GetBacktestByIdForUser(User user, string id);
void DeleteAllBacktests(); void DeleteBacktestByIdForUser(User user, string id);
void DeleteAllBacktestsForUser(User user);
} }

View File

@@ -10,6 +10,14 @@ public interface ICandleRepository
Enums.Ticker ticker, Enums.Ticker ticker,
Enums.Timeframe timeframe, Enums.Timeframe timeframe,
DateTime start); DateTime start);
Task<IList<Candle>> GetCandles(
Enums.TradingExchanges exchange,
Enums.Ticker ticker,
Enums.Timeframe timeframe,
DateTime start,
DateTime end);
Task<IList<Enums.Ticker>> GetTickersAsync( Task<IList<Enums.Ticker>> GetTickersAsync(
Enums.TradingExchanges exchange, Enums.TradingExchanges exchange,
Enums.Timeframe timeframe, Enums.Timeframe timeframe,

View File

@@ -25,7 +25,7 @@ public interface IEvmManager
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker); decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker(); Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker); Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker);
Task<bool> InitAddress(string chainName, string publicAddress, string privateKey); Task<bool> InitAddress(string publicAddress);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey,
string receiverAddress); string receiverAddress);
@@ -34,7 +34,9 @@ public interface IEvmManager
Task<bool> CancelOrders(Account account, Ticker ticker); Task<bool> CancelOrders(Account account, Ticker ticker);
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price,
decimal quantity, decimal? leverage = 1); decimal quantity, decimal? leverage = 1,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
Task<Trade> GetTrade(Account account, string chainName, Ticker ticker); Task<Trade> GetTrade(Account account, string chainName, Ticker ticker);
@@ -44,11 +46,22 @@ public interface IEvmManager
Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker); Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker);
Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction,
decimal price, decimal quantity, decimal? leverage); decimal price, decimal quantity, decimal? leverage,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
Task<decimal> GetFee(string chainName); Task<decimal> GetFee(string chainName);
Task<List<Trade>> GetOrders(Account account, Ticker ticker); Task<List<Trade>> GetOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker); Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
Task<List<FundingRate>> GetFundingRates(); Task<List<FundingRate>> GetFundingRates();
Task<(string Id, string Address)> CreatePrivyWallet(); Task<(string Id, string Address)> CreatePrivyWallet();
/// <summary>
/// Signs a message using the embedded wallet
/// </summary>
/// <param name="embeddedWalletId">The wallet id of the embedded wallet</param>
/// <param name="address">The address of the embedded wallet</param>
/// <param name="message">The message to sign</param>
/// <returns>The signature response</returns>
Task<string> SignMessageAsync(string embeddedWalletId, string address, string message);
} }

View File

@@ -1,4 +1,5 @@
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Repositories; namespace Managing.Application.Abstractions.Repositories;
@@ -10,4 +11,10 @@ public interface ISettingsRepository
IEnumerable<MoneyManagement> GetMoneyManagements(); IEnumerable<MoneyManagement> GetMoneyManagements();
void DeleteMoneyManagement(string name); void DeleteMoneyManagement(string name);
void DeleteMoneyManagements(); void DeleteMoneyManagements();
// User-specific operations (these should eventually replace the above methods)
Task<MoneyManagement> GetMoneyManagementByUser(User user, string name);
IEnumerable<MoneyManagement> GetMoneyManagementsByUser(User user);
void DeleteMoneyManagementByUser(User user, string name);
void DeleteMoneyManagementsByUser(User user);
} }

View File

@@ -1,6 +1,7 @@
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Repositories; namespace Managing.Application.Abstractions.Repositories;
@@ -9,6 +10,8 @@ public interface ITradingRepository
{ {
Scenario GetScenarioByName(string scenario); Scenario GetScenarioByName(string scenario);
void InsertSignal(Signal signal); void InsertSignal(Signal signal);
IEnumerable<Signal> GetSignalsByUser(User user);
Signal GetSignalByIdentifier(string identifier, User user = null);
void InsertPosition(Position position); void InsertPosition(Position position);
void UpdatePosition(Position position); void UpdatePosition(Position position);
Strategy GetStrategyByName(string strategy); Strategy GetStrategyByName(string strategy);

View File

@@ -6,4 +6,5 @@ public interface IUserRepository
{ {
Task<User> GetUserByNameAsync(string name); Task<User> GetUserByNameAsync(string name);
Task InsertUserAsync(User user); Task InsertUserAsync(User user);
Task UpdateUser(User user);
} }

View File

@@ -3,28 +3,54 @@ using Managing.Domain.Backtests;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services namespace Managing.Application.Abstractions.Services
{ {
public interface IBacktester public interface IBacktester
{ {
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, Task<Backtest> RunScalpingBotBacktest(
Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false, Account account,
bool save = false, List<Candle>? initialCandles = null); MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate,
DateTime endDate,
User user = null,
bool isForWatchingOnly = false,
bool save = false,
List<Candle>? initialCandles = null);
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, Task<Backtest> RunFlippingBotBacktest(
Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false, Account account,
bool save = false, List<Candle>? initialCandles = null); MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate,
DateTime endDate,
User user = null,
bool isForWatchingOnly = false,
bool save = false,
List<Candle>? initialCandles = null);
IEnumerable<Backtest> GetBacktests();
bool DeleteBacktest(string id); bool DeleteBacktest(string id);
bool DeleteBacktests(); bool DeleteBacktests();
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance); Timeframe timeframe, List<Candle> candles, decimal balance, User user = null);
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance); Timeframe timeframe, List<Candle> candles, decimal balance, User user = null);
// User-specific operations
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);
Backtest GetBacktestByIdForUser(User user, string id);
bool DeleteBacktestByUser(User user, string id);
bool DeleteBacktestsByUser(User user);
} }
} }

View File

@@ -19,7 +19,9 @@ public interface IExchangeService
bool reduceOnly = false, bool reduceOnly = false,
bool isForPaperTrading = false, bool isForPaperTrading = false,
DateTime? currentDate = null, DateTime? currentDate = null,
bool ioc = true); bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false); Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false); Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
@@ -47,6 +49,9 @@ public interface IExchangeService
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
Timeframe timeframe); Timeframe timeframe);
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
Timeframe timeframe, DateTime endDate);
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction); decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
Orderbook GetOrderbook(Account account, Ticker ticker); Orderbook GetOrderbook(Account account, Ticker ticker);

View File

@@ -24,7 +24,7 @@ public class BaseTests
_moneyManagementService = new Mock<IMoneyManagementService>(); _moneyManagementService = new Mock<IMoneyManagementService>();
MoneyManagement = new MoneyManagement() MoneyManagement = new MoneyManagement()
{ {
BalanceAtRisk = 0.30m, // 30% BalanceAtRisk = 1m, // 30%
Leverage = 2, // x2 Leverage = 2, // x2
Timeframe = Timeframe.FifteenMinutes, Timeframe = Timeframe.FifteenMinutes,
StopLoss = 0.008m, // 0.8% StopLoss = 0.008m, // 0.8%
@@ -32,7 +32,7 @@ public class BaseTests
Name = "Default MM" Name = "Default MM"
}; };
_ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny<string>())) _ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny<Domain.Users.User>(), It.IsAny<string>()))
.Returns(Task.FromResult(MoneyManagement)); .Returns(Task.FromResult(MoneyManagement));
_accountService.Setup(a => a.GetAccount(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>())) _accountService.Setup(a => a.GetAccount(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))

View File

@@ -9,6 +9,7 @@ using Managing.Core;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using MongoDB.Driver.Linq;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xunit; using Xunit;
@@ -30,6 +31,7 @@ namespace Managing.Application.Tests
{ {
var backtestRepository = new Mock<IBacktestRepository>().Object; var backtestRepository = new Mock<IBacktestRepository>().Object;
var discordService = new Mock<IMessengerService>().Object; var discordService = new Mock<IMessengerService>().Object;
var scenarioService = new Mock<IScenarioService>().Object;
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
var botService = new Mock<IBotService>().Object; var botService = new Mock<IBotService>().Object;
@@ -40,13 +42,14 @@ namespace Managing.Application.Tests
_accountService.Object, _accountService.Object,
_tradingService.Object, _tradingService.Object,
botService); botService);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger); _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
scenarioService);
_elapsedTimes = new List<double>(); _elapsedTimes = new List<double>();
} }
[Theory] [Theory]
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -10)] [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -10)]
public void SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, public async Task SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe,
int days) int days)
{ {
// Arrange // Arrange
@@ -57,8 +60,9 @@ namespace Managing.Application.Tests
FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json"); FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json");
// Act // Act
var backtestResult = _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario, var backtestResult = await _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario,
timeframe, Convert.ToDouble(days), 1000, initialCandles: localCandles.TakeLast(500).ToList()); timeframe, 1000, new DateTime().AddDays(-3), DateTime.UtcNow,
initialCandles: localCandles.TakeLast(500).ToList());
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None); var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json); File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
@@ -78,7 +82,8 @@ namespace Managing.Application.Tests
//[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.ThirtyMinutes, -4)] //[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.ThirtyMinutes, -4)]
//[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.FifteenMinutes, -4)] //[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.FifteenMinutes, -4)]
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -14)] [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -14)]
public void ScalpingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, public async Task ScalpingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker,
Timeframe timeframe,
int days) int days)
{ {
// Arrange // Arrange
@@ -87,8 +92,8 @@ namespace Managing.Application.Tests
scenario.AddStrategy(strategy); scenario.AddStrategy(strategy);
// Act // Act
var backtestResult = _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario, var backtestResult = await _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario,
timeframe, Convert.ToDouble(days), 1000); timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
//WriteCsvReport(backtestResult.GetStringReport()); //WriteCsvReport(backtestResult.GetStringReport());
// Assert // Assert
@@ -99,7 +104,7 @@ namespace Managing.Application.Tests
[Theory] [Theory]
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -8)] [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -8)]
public void MacdCross_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, public async Task MacdCross_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe,
int days) int days)
{ {
// Arrange // Arrange
@@ -118,8 +123,8 @@ namespace Managing.Application.Tests
}; };
// Act // Act
var backtestResult = _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario, var backtestResult = await _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario,
timeframe, Convert.ToDouble(days), 1000); timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
WriteCsvReport(backtestResult.GetStringReport()); WriteCsvReport(backtestResult.GetStringReport());
// Assert // Assert
@@ -190,9 +195,9 @@ namespace Managing.Application.Tests
{ {
BotType.SimpleBot => throw new NotImplementedException(), BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement, BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement,
scenario, timeframe, candles, 1000), scenario, timeframe, candles, 1000, null).Result,
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement, BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
scenario, timeframe, candles, 1000), scenario, timeframe, candles, 1000, null).Result,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
timer.Stop(); timer.Stop();
@@ -299,9 +304,9 @@ namespace Managing.Application.Tests
{ {
BotType.SimpleBot => throw new NotImplementedException(), BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement, BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement,
scenario, timeframe, candles, 1000), scenario, timeframe, candles, 1000, null).Result,
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement, BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
scenario, timeframe, candles, 1000), scenario, timeframe, candles, 1000, null).Result,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };

View File

@@ -7,13 +7,16 @@ using Managing.Infrastructure.Databases.InfluxDb;
using Managing.Infrastructure.Databases.InfluxDb.Models; using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Evm; using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions; using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Services; using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs; using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges; using Managing.Infrastructure.Exchanges;
using Managing.Infrastructure.Exchanges.Abstractions; using Managing.Infrastructure.Exchanges.Abstractions;
using Managing.Infrastructure.Exchanges.Exchanges; using Managing.Infrastructure.Exchanges.Exchanges;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using Nethereum.Web3;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Tests namespace Managing.Application.Tests
@@ -34,7 +37,7 @@ namespace Managing.Application.Tests
Chainlink, Chainlink,
GbcFeed GbcFeed
}; };
var evmManager = new EvmManager(Subgraphs); var evmManager = new EvmManager(Subgraphs, CreateWebProxyService());
var evmProcessor = new EvmProcessor(new Mock<ILogger<EvmProcessor>>().Object, evmManager); var evmProcessor = new EvmProcessor(new Mock<ILogger<EvmProcessor>>().Object, evmManager);
var exchangeProcessors = new List<IExchangeProcessor>() var exchangeProcessors = new List<IExchangeProcessor>()
@@ -44,7 +47,8 @@ namespace Managing.Application.Tests
evmProcessor evmProcessor
}; };
return new ExchangeService(loggerFactory.CreateLogger<ExchangeService>(), GetCandleRepository(), exchangeProcessors); return new ExchangeService(loggerFactory.CreateLogger<ExchangeService>(), GetCandleRepository(),
exchangeProcessors);
} }
public static ILogger<TradingBot> CreateTradingBotLogger() public static ILogger<TradingBot> CreateTradingBotLogger()
@@ -72,14 +76,22 @@ namespace Managing.Application.Tests
{ {
var settings = new InfluxDbSettings() var settings = new InfluxDbSettings()
{ {
Url = "http://localhost:8086/", Url = "http://localhost:8086/",
Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==", Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==",
Organization = "managing-org" Organization = "managing-org"
}; };
var influxdb = new InfluxDbRepository(settings); var influxdb = new InfluxDbRepository(settings);
var candleRepository = new CandleRepository(influxdb, CreateCandleRepositoryLogger()); var candleRepository = new CandleRepository(influxdb, CreateCandleRepositoryLogger());
return candleRepository; return candleRepository;
} }
// Helper method to create Web3ProxyService for tests
public static IWeb3ProxyService CreateWebProxyService(string baseUrl = "http://localhost:4111/api/")
{
var settings = new Web3ProxySettings { BaseUrl = baseUrl };
var options = Options.Create(settings);
return new Web3ProxyService(options);
}
} }
} }

View File

@@ -216,16 +216,16 @@ public class StatisticService : IStatisticService
MaxDegreeOfParallelism = 2 MaxDegreeOfParallelism = 2
}; };
_ = Parallel.ForEach(tickers, options, ticker => _ = Parallel.ForEach(tickers, options, async ticker =>
{ {
spotlight.TickerSignals.Add(new TickerSignal spotlight.TickerSignals.Add(new TickerSignal
{ {
Ticker = ticker, Ticker = ticker,
FiveMinutes = GetSignals(account, scenario, ticker, Timeframe.FiveMinutes), FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
FifteenMinutes = GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes), FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
OneHour = GetSignals(account, scenario, ticker, Timeframe.OneHour), OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour),
FourHour = GetSignals(account, scenario, ticker, Timeframe.FourHour), FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour),
OneDay = GetSignals(account, scenario, ticker, Timeframe.OneDay) OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay)
}); });
}); });
@@ -237,7 +237,7 @@ public class StatisticService : IStatisticService
_statisticRepository.UpdateSpotlightOverview(overview); _statisticRepository.UpdateSpotlightOverview(overview);
} }
private List<Signal> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe) private async Task<List<Signal>> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe)
{ {
try try
{ {
@@ -250,14 +250,15 @@ public class StatisticService : IStatisticService
TakeProfit = 0.02m TakeProfit = 0.02m
}; };
var backtest = _backtester.RunScalpingBotBacktest( var backtest = await _backtester.RunScalpingBotBacktest(
account, account,
moneyManagement, moneyManagement,
ticker, ticker,
scenario, scenario,
timeframe, timeframe,
CandleExtensions.GetMinimalDays(timeframe),
1000, 1000,
DateTime.Now.AddDays(-7),
DateTime.Now,
isForWatchingOnly: true); isForWatchingOnly: true);
return backtest.Signals; return backtest.Signals;

View File

@@ -1,4 +1,5 @@
using Managing.Common; using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
@@ -14,6 +15,7 @@ public interface IBotService
List<ITradingBot> GetActiveBots(); List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots(); IEnumerable<BotBackup> GetSavedBots();
void StartBotFromBackup(BotBackup backupBot); void StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string name);
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly); string scenario, Enums.Timeframe interval, bool isForWatchingOnly);

View File

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

View File

@@ -1,5 +1,6 @@
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions namespace Managing.Application.Abstractions
@@ -30,5 +31,31 @@ namespace Managing.Application.Abstractions
bool UpdateStrategy(StrategyType strategyType, string name, int? period, int? fastPeriods, int? slowPeriods, bool UpdateStrategy(StrategyType strategyType, string name, int? period, int? fastPeriods, int? slowPeriods,
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods); int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
IEnumerable<Scenario> GetScenariosByUser(User user);
Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
IEnumerable<Strategy> GetStrategiesByUser(User user);
bool DeleteStrategyByUser(User user, string name);
bool DeleteScenarioByUser(User user, string name);
Scenario GetScenarioByUser(User user, string name);
Strategy CreateStrategyForUser(User user,
StrategyType 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);
bool DeleteStrategiesByUser(User user);
bool DeleteScenariosByUser(User user);
bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
bool UpdateStrategyByUser(User user, StrategyType strategyType, string name, int? period, int? fastPeriods,
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
} }
} }

View File

@@ -4,4 +4,5 @@ public interface ISettingsService
{ {
bool SetupSettings(); bool SetupSettings();
Task<bool> ResetSettings(); Task<bool> ResetSettings();
Task<bool> CreateDefaultConfiguration(Domain.Users.User user);
} }

View File

@@ -1,4 +1,5 @@
using Managing.Core.FixedSizedQueue; using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
@@ -34,5 +35,6 @@ namespace Managing.Application.Abstractions
void LoadStrategies(IEnumerable<IStrategy> strategies); void LoadStrategies(IEnumerable<IStrategy> strategies);
void LoadScenario(string scenarioName); void LoadScenario(string scenarioName);
void UpdateStrategiesValues(); void UpdateStrategiesValues();
Task LoadAccount();
} }
} }

View File

@@ -52,8 +52,10 @@ public class AccountService : IAccountService
else if (request.Exchange == Enums.TradingExchanges.Evm else if (request.Exchange == Enums.TradingExchanges.Evm
&& request.Type == Enums.AccountType.Privy) && request.Type == Enums.AccountType.Privy)
{ {
if (string.IsNullOrEmpty(request.Key) || string.IsNullOrEmpty(request.Secret)) if (string.IsNullOrEmpty(request.Key))
{ {
// No key provided, create new privy embedded wallet.
// TODO : Fix it to create privy wallet
var privyClient = await _evmManager.CreatePrivyWallet(); var privyClient = await _evmManager.CreatePrivyWallet();
request.Key = privyClient.Address; request.Key = privyClient.Address;
request.Secret = privyClient.Id; request.Secret = privyClient.Id;

View File

@@ -14,6 +14,7 @@ using Managing.Domain.Strategies.Base;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
using Managing.Domain.Users;
namespace Managing.Application.Backtesting namespace Managing.Application.Backtesting
{ {
@@ -42,21 +43,25 @@ namespace Managing.Application.Backtesting
{ {
var simplebot = _botFactory.CreateSimpleBot("scenario", workflow); var simplebot = _botFactory.CreateSimpleBot("scenario", workflow);
Backtest result = null; Backtest result = null;
if (save) if (save && result != null)
{ {
_backtestRepository.InsertBacktest(result); // Simple bot backtest not implemented yet, would need user
// _backtestRepository.InsertBacktestForUser(null, result);
} }
return result; return result;
} }
public Backtest RunScalpingBotBacktest(Account account, public async Task<Backtest> RunScalpingBotBacktest(
Account account,
MoneyManagement moneyManagement, MoneyManagement moneyManagement,
Ticker ticker, Ticker ticker,
Scenario scenario, Scenario scenario,
Timeframe timeframe, Timeframe timeframe,
double days,
decimal balance, decimal balance,
DateTime startDate,
DateTime endDate,
User user = null,
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle> initialCandles = null) List<Candle> initialCandles = null)
@@ -64,21 +69,36 @@ namespace Managing.Application.Backtesting
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, isForWatchingOnly); timeframe, isForWatchingOnly);
scalpingBot.LoadScenario(scenario.Name); scalpingBot.LoadScenario(scenario.Name);
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, days); await scalpingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
var result = GetBacktestingResult(ticker, scenario, timeframe, scalpingBot, candles, balance, account, var result = GetBacktestingResult(ticker, scenario, timeframe, scalpingBot, candles, balance, account,
moneyManagement); moneyManagement);
if (user != null)
{
result.User = user;
}
// Set start and end dates
result.StartDate = startDate;
result.EndDate = endDate;
if (save) if (save)
{ {
_backtestRepository.InsertBacktest(result); _backtestRepository.InsertBacktestForUser(user, result);
} }
return result; return result;
} }
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe, double days) private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{ {
var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, List<Candle> candles;
DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result;
// Use specific date range
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0) if (candles == null || candles.Count == 0)
throw new Exception($"No candles for {ticker} on {account.Exchange}"); throw new Exception($"No candles for {ticker} on {account.Exchange}");
@@ -86,46 +106,85 @@ namespace Managing.Application.Backtesting
return candles; return candles;
} }
public Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, public async Task<Backtest> RunFlippingBotBacktest(
Scenario scenario, Timeframe timeframe, Account account,
double days, decimal balance, bool isForWatchingOnly = false, bool save = false, MoneyManagement moneyManagement,
Ticker ticker,
Scenario scenario,
Timeframe timeframe,
decimal balance,
DateTime startDate,
DateTime endDate,
User user = null,
bool isForWatchingOnly = false,
bool save = false,
List<Candle> initialCandles = null) List<Candle> initialCandles = null)
{ {
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false); timeframe, false);
flippingBot.LoadScenario(scenario.Name); flippingBot.LoadScenario(scenario.Name);
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, days); await flippingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
var result = GetBacktestingResult(ticker, scenario, timeframe, flippingBot, candles, balance, account, var result = GetBacktestingResult(ticker, scenario, timeframe, flippingBot, candles, balance, account,
moneyManagement); moneyManagement);
if (user != null)
{
result.User = user;
}
// Set start and end dates
result.StartDate = startDate;
result.EndDate = endDate;
if (save) if (save)
{ {
_backtestRepository.InsertBacktest(result); _backtestRepository.InsertBacktestForUser(user, result);
} }
return result; return result;
} }
public Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement,
Timeframe timeframe, List<Candle> candles, decimal balance) Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false); timeframe, false);
bot.LoadScenario(scenario.Name); bot.LoadScenario(scenario.Name);
await bot.LoadAccount();
var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account, var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
moneyManagement); moneyManagement);
if (user != null)
{
result.User = user;
}
return result; return result;
} }
public Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement,
Timeframe timeframe, List<Candle> candles, decimal balance) Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false); timeframe, false);
bot.LoadScenario(scenario.Name); bot.LoadScenario(scenario.Name);
await bot.LoadAccount();
var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account, var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
moneyManagement); moneyManagement);
if (user != null)
{
result.User = user;
}
return result; return result;
} }
@@ -177,10 +236,8 @@ namespace Managing.Application.Backtesting
maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime
); );
// Then calculate the score
var score = BacktestScorer.CalculateTotalScore(scoringParams); var score = BacktestScorer.CalculateTotalScore(scoringParams);
var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles, var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles,
bot.BotType, account.Name) bot.BotType, account.Name)
{ {
@@ -197,7 +254,6 @@ namespace Managing.Application.Backtesting
Score = score Score = score
}; };
return result; return result;
} }
@@ -252,16 +308,14 @@ namespace Managing.Application.Backtesting
return strategiesValues; return strategiesValues;
} }
public IEnumerable<Backtest> GetBacktests()
{
return _backtestRepository.GetBacktests();
}
public bool DeleteBacktest(string id) public bool DeleteBacktest(string id)
{ {
try try
{ {
_backtestRepository.DeleteBacktestById(id); // Since we no longer have a general DeleteBacktestById method in the repository,
// this should be implemented using DeleteBacktestByIdForUser with null
_backtestRepository.DeleteBacktestByIdForUser(null, id);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -275,8 +329,111 @@ namespace Managing.Application.Backtesting
{ {
try try
{ {
_backtestRepository.DeleteAllBacktests(); // Since we no longer have a general DeleteAllBacktests method in the repository,
//_backtestRepository.DropCollection(); // this should be implemented using DeleteAllBacktestsForUser with null
_backtestRepository.DeleteAllBacktestsForUser(null);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public async Task<IEnumerable<Backtest>> GetBacktestsByUser(User user)
{
var backtests = _backtestRepository.GetBacktestsByUser(user).ToList();
// For each backtest, ensure candles are loaded
foreach (var backtest in backtests)
{
// If the backtest has no candles or only a few sample candles, retrieve them
if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
{
try
{
var candles = await _exchangeService.GetCandlesInflux(
user.Accounts.First().Exchange,
backtest.Ticker,
backtest.StartDate,
backtest.Timeframe,
backtest.EndDate);
if (candles != null && candles.Count > 0)
{
backtest.Candles = candles;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", backtest.Id);
// Continue with the next backtest if there's an error
}
}
}
return backtests;
}
public Backtest GetBacktestByIdForUser(User user, string id)
{
// Get the backtest from the repository
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
if (backtest == null)
return null;
// If the backtest has no candles or only a few sample candles, retrieve them
if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
{
try
{
// Get the account
var account = new Account { Name = backtest.AccountName, Exchange = TradingExchanges.Binance };
// Use the stored start and end dates to retrieve candles
var candles = _exchangeService.GetCandlesInflux(
account.Exchange,
backtest.Ticker,
backtest.StartDate,
backtest.Timeframe,
backtest.EndDate).Result;
if (candles != null && candles.Count > 0)
{
backtest.Candles = candles;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", id);
// Return the backtest without candles if there's an error
}
}
return backtest;
}
public bool DeleteBacktestByUser(User user, string id)
{
try
{
_backtestRepository.DeleteBacktestByIdForUser(user, id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteBacktestsByUser(User user)
{
try
{
_backtestRepository.DeleteAllBacktestsForUser(user);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -101,6 +101,7 @@ public class TradingBot : Bot, ITradingBot
public override async void Start() public override async void Start()
{ {
base.Start(); base.Start();
// Load account synchronously
await LoadAccount(); await LoadAccount();
if (!IsForBacktest) if (!IsForBacktest)
@@ -127,7 +128,7 @@ public class TradingBot : Bot, ITradingBot
Fee = TradingService.GetFee(Account, IsForBacktest); Fee = TradingService.GetFee(Account, IsForBacktest);
} }
private async Task LoadAccount() public async Task LoadAccount()
{ {
var account = await AccountService.GetAccount(AccountName, false, true); var account = await AccountService.GetAccount(AccountName, false, true);
if (account == null) if (account == null)
@@ -225,6 +226,7 @@ public class TradingBot : Bot, ITradingBot
if (!OptimizedCandles.Any(c => c.Date == candle.Date)) if (!OptimizedCandles.Any(c => c.Date == candle.Date))
{ {
OptimizedCandles.Enqueue(candle); OptimizedCandles.Enqueue(candle);
Candles.Add(candle);
await UpdateSignals(OptimizedCandles); await UpdateSignals(OptimizedCandles);
} }
} }
@@ -235,9 +237,9 @@ public class TradingBot : Bot, ITradingBot
private async Task UpdateSignals(FixedSizeQueue<Candle> candles) private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
{ {
var signal = TradingBox.GetSignal(candles.ToHashSet(), Strategies, Signals, Scenario.LoopbackPeriod); var signal = TradingBox.GetSignal(candles.ToHashSet(), Strategies, Signals, Scenario.LoopbackPeriod);
if (signal == null) return; if (signal == null) return;
signal.User = Account.User;
await AddSignal(signal); await AddSignal(signal);
} }
@@ -246,8 +248,8 @@ public class TradingBot : Bot, ITradingBot
{ {
Signals.Add(signal); Signals.Add(signal);
if (!IsForBacktest) // if (!IsForBacktest)
TradingService.InsertSignal(signal); // TradingService.InsertSignal(signal);
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest)) if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
signal.Status = SignalStatus.Expired; signal.Status = SignalStatus.Expired;
@@ -274,6 +276,7 @@ public class TradingBot : Bot, ITradingBot
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime())) foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{ {
OptimizedCandles.Enqueue(candle); OptimizedCandles.Enqueue(candle);
Candles.Add(candle);
} }
} }
@@ -497,7 +500,7 @@ public class TradingBot : Bot, ITradingBot
MoneyManagement, MoneyManagement,
signal.Direction, signal.Direction,
Ticker, Ticker,
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot , IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot,
signal.Date, signal.Date,
IsForBacktest, IsForBacktest,
lastPrice, lastPrice,
@@ -643,12 +646,23 @@ public class TradingBot : Bot, ITradingBot
{ {
try try
{ {
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker); var test = await ExchangeService.CancelOrder(Account, Ticker);
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker);
if (openOrders.Any())
{
Logger.LogInformation($"Canceling all orders for {Ticker}");
await ExchangeService.CancelOrder(Account, Ticker);
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
}
{
Logger.LogInformation($"No need to cancel orders for {Ticker}");
}
} }
catch (Exception ex) catch (Exception ex)
{ {
// Todo handle exception from evm
Logger.LogError(ex, "Error during cancelOrders"); Logger.LogError(ex, "Error during cancelOrders");
} }
} }

View File

@@ -97,11 +97,13 @@ namespace Managing.Application.ManageBot
public List<ITradingBot> GetActiveBots() public List<ITradingBot> GetActiveBots()
{ {
return _botTasks.Values var bots = _botTasks.Values
.Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType)) .Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType))
.Select(wrapper => wrapper.BotInstance as ITradingBot) .Select(wrapper => wrapper.BotInstance as ITradingBot)
.Where(bot => bot != null) .Where(bot => bot != null)
.ToList(); .ToList();
return bots;
} }
public IEnumerable<BotBackup> GetSavedBots() public IEnumerable<BotBackup> GetSavedBots()
@@ -227,12 +229,10 @@ namespace Managing.Application.ManageBot
public void ToggleIsForWatchingOnly(string botName) public void ToggleIsForWatchingOnly(string botName)
{ {
if (_botTasks.TryGetValue(botName, out var botWrapper)) if (_botTasks.TryGetValue(botName, out var botTaskWrapper) &&
botTaskWrapper.BotInstance is ITradingBot tradingBot)
{ {
if (botWrapper.BotInstance is ITradingBot bot) tradingBot.ToggleIsForWatchOnly().Wait();
{
bot.IsForWatchingOnly = !bot.IsForWatchingOnly;
}
} }
} }

View File

@@ -1,4 +1,5 @@
using MediatR; using MediatR;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.ManageBot.Commands namespace Managing.Application.ManageBot.Commands
@@ -13,6 +14,7 @@ namespace Managing.Application.ManageBot.Commands
public string Scenario { get; internal set; } public string Scenario { get; internal set; }
public string AccountName { get; internal set; } public string AccountName { get; internal set; }
public string MoneyManagementName { get; internal set; } public string MoneyManagementName { get; internal set; }
public User User { get; internal set; }
public StartBotCommand(BotType botType, public StartBotCommand(BotType botType,
string name, string name,
@@ -21,6 +23,7 @@ namespace Managing.Application.ManageBot.Commands
Timeframe timeframe, Timeframe timeframe,
string accountName, string accountName,
string moneyManagementName, string moneyManagementName,
User user,
bool isForWatchingOnly = false) bool isForWatchingOnly = false)
{ {
BotType = botType; BotType = botType;
@@ -31,6 +34,7 @@ namespace Managing.Application.ManageBot.Commands
IsForWatchingOnly = isForWatchingOnly; IsForWatchingOnly = isForWatchingOnly;
AccountName = accountName; AccountName = accountName;
MoneyManagementName = moneyManagementName; MoneyManagementName = moneyManagementName;
User = user;
} }
} }
} }

View File

@@ -22,7 +22,7 @@ namespace Managing.Application.ManageBot
public Task<string> Handle(StartBotCommand request, CancellationToken cancellationToken) public Task<string> Handle(StartBotCommand request, CancellationToken cancellationToken)
{ {
BotStatus botStatus = BotStatus.Down; BotStatus botStatus = BotStatus.Down;
var moneyManagement = _moneyManagementService.GetMoneyMangement(request.MoneyManagementName).Result; var moneyManagement = _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName).Result;
switch (request.BotType) switch (request.BotType)
{ {
case BotType.SimpleBot: case BotType.SimpleBot:

View File

@@ -2,6 +2,7 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Users;
namespace Managing.Application.MoneyManagements; namespace Managing.Application.MoneyManagements;
@@ -18,42 +19,110 @@ public class MoneyManagementService : IMoneyManagementService
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
} }
public async Task<MoneyManagement> CreateOrUpdateMoneyManagement(MoneyManagement request) public async Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request)
{ {
var moneyManagement = await _settingsRepository.GetMoneyManagement(request.Name); // Try to get user-specific strategy first
var moneyManagement = await _settingsRepository.GetMoneyManagementByUser(user, request.Name);
// Fall back to regular lookup if user-specific endpoint is not implemented
if (moneyManagement == null)
{
moneyManagement = await _settingsRepository.GetMoneyManagement(request.Name);
// If found by name but doesn't belong to this user, treat as new
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
moneyManagement = null;
}
}
if (moneyManagement == null) if (moneyManagement == null)
{ {
request.User = user;
await _settingsRepository.InsertMoneyManagement(request); await _settingsRepository.InsertMoneyManagement(request);
return request;
} }
else else
{ {
// Additional check to ensure user's ownership
if (moneyManagement.User?.Name != user.Name)
{
throw new UnauthorizedAccessException("You do not have permission to update this money management strategy.");
}
moneyManagement.StopLoss = request.StopLoss; moneyManagement.StopLoss = request.StopLoss;
moneyManagement.TakeProfit = request.TakeProfit; moneyManagement.TakeProfit = request.TakeProfit;
moneyManagement.BalanceAtRisk = request.BalanceAtRisk; moneyManagement.BalanceAtRisk = request.BalanceAtRisk;
moneyManagement.Leverage = request.Leverage; moneyManagement.Leverage = request.Leverage;
moneyManagement.Timeframe = request.Timeframe; moneyManagement.Timeframe = request.Timeframe;
moneyManagement.User = user;
_settingsRepository.UpdateMoneyManagement(moneyManagement); _settingsRepository.UpdateMoneyManagement(moneyManagement);
return moneyManagement;
}
}
public IEnumerable<MoneyManagement> GetMoneyMangements(User user)
{
try
{
// Try to use user-specific repository method first
return _settingsRepository.GetMoneyManagementsByUser(user);
}
catch
{
// Fall back to filtering if user-specific endpoint is not implemented
return _settingsRepository.GetMoneyManagements()
.Where(mm => mm.User?.Name == user.Name);
}
}
public async Task<MoneyManagement> GetMoneyMangement(User user, string name)
{
MoneyManagement moneyManagement;
try
{
// Try to use user-specific repository method first
moneyManagement = await _settingsRepository.GetMoneyManagementByUser(user, name);
}
catch
{
// Fall back to regular lookup if user-specific endpoint is not implemented
moneyManagement = await _settingsRepository.GetMoneyManagement(name);
// Filter by user
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
moneyManagement = null;
}
} }
return moneyManagement; return moneyManagement;
} }
public IEnumerable<MoneyManagement> GetMoneyMangements() public bool DeleteMoneyManagement(User user, string name)
{
return _settingsRepository.GetMoneyManagements();
}
public async Task<MoneyManagement> GetMoneyMangement(string name)
{
return await _settingsRepository.GetMoneyManagement(name);
}
public bool DeleteMoneyManagement(string name)
{ {
try try
{ {
_settingsRepository.DeleteMoneyManagement(name); try
{
// Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementByUser(user, name);
}
catch
{
// Fall back to verifying user ownership before deletion
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result;
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
throw new UnauthorizedAccessException("You do not have permission to delete this money management strategy.");
}
_settingsRepository.DeleteMoneyManagement(name);
}
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -63,11 +132,22 @@ public class MoneyManagementService : IMoneyManagementService
} }
} }
public bool DeleteMoneyManagements() public bool DeleteMoneyManagements(User user)
{ {
try try
{ {
_settingsRepository.DeleteMoneyManagements(); try
{
// Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementsByUser(user);
}
catch
{
// This fallback is not ideal as it would delete all money managements regardless of user
// In a real implementation, we would need a filtered repository method
_logger.LogWarning("DeleteMoneyManagementsByUser not implemented, cannot delete user-specific money managements");
}
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -5,6 +5,8 @@ using Managing.Domain.Strategies;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MongoDB.Driver; using MongoDB.Driver;
using static Managing.Common.Enums; using static Managing.Common.Enums;
using System.Collections.Generic;
using Managing.Domain.Users;
namespace Managing.Application.Scenarios namespace Managing.Application.Scenarios
{ {
@@ -186,5 +188,166 @@ namespace Managing.Application.Scenarios
return false; return false;
} }
} }
// User-specific methods implementation
public IEnumerable<Scenario> GetScenariosByUser(User user)
{
var scenarios = _tradingService.GetScenarios();
return scenarios.Where(s => s.User?.Name == user.Name);
}
public Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
{
var scenario = new Scenario(name, loopbackPeriod ?? 1)
{
User = user
};
foreach (var strategyName in strategies)
{
var strategy = _tradingService.GetStrategyByName(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddStrategy(strategy);
}
}
_tradingService.InsertScenario(scenario);
return scenario;
}
public IEnumerable<Strategy> GetStrategiesByUser(User user)
{
var strategies = _tradingService.GetStrategies();
return strategies.Where(s => s.User?.Name == user.Name);
}
public bool DeleteStrategyByUser(User user, string name)
{
var strategy = _tradingService.GetStrategyByName(name);
if (strategy != null && strategy.User?.Name == user.Name)
{
_tradingService.DeleteStrategy(strategy.Name);
return true;
}
return false;
}
public bool DeleteScenarioByUser(User user, string name)
{
var scenario = _tradingService.GetScenarioByName(name);
if (scenario != null && scenario.User?.Name == user.Name)
{
_tradingService.DeleteScenario(scenario.Name);
return true;
}
return false;
}
public Scenario GetScenarioByUser(User user, string name)
{
var scenario = _tradingService.GetScenarioByName(name);
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
}
public Strategy CreateStrategyForUser(User user, StrategyType 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,
multiplier, stochPeriods, smoothPeriods, cyclePeriods);
// Set the user
strategy.User = user;
// Update the strategy to save the user property
_tradingService.UpdateStrategy(strategy);
return strategy;
}
public bool DeleteStrategiesByUser(User user)
{
try
{
var strategies = GetStrategiesByUser(user);
foreach (var strategy in strategies)
{
_tradingService.DeleteStrategy(strategy.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteScenariosByUser(User user)
{
try
{
var scenarios = GetScenariosByUser(user);
foreach (var scenario in scenarios)
{
_tradingService.DeleteScenario(scenario.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
{
var scenario = _tradingService.GetScenarioByName(name);
if (scenario == null || scenario.User?.Name != user.Name)
{
return false;
}
scenario.Strategies.Clear();
scenario.LoopbackPeriod = loopbackPeriod ?? 1;
foreach (var strategyName in strategies)
{
var strategy = _tradingService.GetStrategyByName(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddStrategy(strategy);
}
}
_tradingService.UpdateScenario(scenario);
return true;
}
public bool UpdateStrategyByUser(User user, StrategyType strategyType, string name, int? period,
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{
var strategy = _tradingService.GetStrategyByName(name);
if (strategy == null || strategy.User?.Name != user.Name)
{
return false;
}
// Use the existing update strategy logic
var result = UpdateStrategy(strategyType, name, period, fastPeriods, slowPeriods,
signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods);
return result;
}
} }
} }

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -41,10 +42,6 @@ public class SettingsService : ISettingsService
throw new Exception("Cannot delete all strategies"); throw new Exception("Cannot delete all strategies");
} }
if (!_moneyManagementService.DeleteMoneyManagements())
{
throw new Exception("Cannot delete all money management settings");
}
if (!SetupSettings()) if (!SetupSettings())
{ {
@@ -58,10 +55,10 @@ public class SettingsService : ISettingsService
{ {
try try
{ {
SetupMoneyManagementsSeed(Timeframe.FiveMinutes); // SetupMoneyManagementsSeed(Timeframe.FiveMinutes);
SetupMoneyManagementsSeed(Timeframe.FifteenMinutes); // SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
SetupMoneyManagementsSeed(Timeframe.OneHour); // SetupMoneyManagementsSeed(Timeframe.OneHour);
SetupMoneyManagementsSeed(Timeframe.OneDay); // SetupMoneyManagementsSeed(Timeframe.OneDay);
SetupScenariosSeed(); SetupScenariosSeed();
} }
catch (Exception ex) catch (Exception ex)
@@ -73,20 +70,20 @@ public class SettingsService : ISettingsService
return true; return true;
} }
private async void SetupMoneyManagementsSeed(Timeframe timeframe) // private async void SetupMoneyManagementsSeed(Timeframe timeframe)
{ // {
var moneyManagement = new MoneyManagement() // var moneyManagement = new MoneyManagement()
{ // {
Timeframe = timeframe, // Timeframe = timeframe,
BalanceAtRisk = 0.05m, // BalanceAtRisk = 0.05m,
Leverage = 1, // Leverage = 1,
StopLoss = 0.021m, // StopLoss = 0.021m,
TakeProfit = 0.042m, // TakeProfit = 0.042m,
Name = $"{timeframe}-MediumRisk", // Name = $"{timeframe}-MediumRisk",
}; // };
//
await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement); // await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
} // }
private void SetupScenariosSeed() private void SetupScenariosSeed()
{ {
@@ -190,4 +187,58 @@ public class SettingsService : ISettingsService
period: 200); period: 200);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
public async Task<bool> CreateDefaultConfiguration(User user)
{
try
{
if (user == null)
throw new ArgumentNullException(nameof(user), "User cannot be null");
// Create default Money Management
var defaultMoneyManagement = new MoneyManagement
{
Name = "Personal-Hourly",
Timeframe = Timeframe.OneHour,
BalanceAtRisk = 25, // 25%
StopLoss = 2, // 2%
TakeProfit = 4, // 4%
Leverage = 1,
User = user
};
// Format the percentage values correctly
defaultMoneyManagement.FormatPercentage();
await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement);
// Create default Strategy (StcTrend)
var defaultStrategy = _scenarioService.CreateStrategyForUser(
user,
StrategyType.Stc,
"Stc",
period: null,
fastPeriods: 23,
slowPeriods: 50,
null,
null,
cyclePeriods: 10
);
// Create default Scenario containing the strategy
var strategyNames = new List<string> { defaultStrategy.Name };
var defaultScenario = _scenarioService.CreateScenarioForUser(
user,
"STC Scenario",
strategyNames
);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating default configuration");
return false;
}
}
} }

View File

@@ -3,6 +3,9 @@ using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands; using Managing.Application.Trading.Commands;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Trading; namespace Managing.Application.Trading;
@@ -10,40 +13,51 @@ namespace Managing.Application.Trading;
public class ClosePositionCommandHandler( public class ClosePositionCommandHandler(
IExchangeService exchangeService, IExchangeService exchangeService,
IAccountService accountService, IAccountService accountService,
ITradingService tradingService) ITradingService tradingService,
ILogger<ClosePositionCommandHandler> logger = null)
: ICommandHandler<ClosePositionCommand, Position> : ICommandHandler<ClosePositionCommand, Position>
{ {
public async Task<Position> Handle(ClosePositionCommand request) public async Task<Position> Handle(ClosePositionCommand request)
{ {
// Get Trade try
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null)
{ {
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result; // Get Trade
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null)
{
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
return request.Position;
}
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Close market
var closedPosition =
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
var closeRequestedOrders =
isForPaperTrading || (await exchangeService.CancelOrder(account, request.Position.Ticker));
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
{
request.Position.Status = PositionStatus.Finished;
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position);
}
return request.Position; return request.Position;
} }
catch (Exception ex)
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Close market
var closedPosition =
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
var closeRequestedOrders =
isForPaperTrading || (await exchangeService.CancelOrder(account, request.Position.Ticker));
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
{ {
request.Position.Status = PositionStatus.Finished; // Log the error - regardless of the error type
request.Position.ProfitAndLoss = logger?.LogError(ex, "Error closing position: {Message}", ex.Message);
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position);
}
return request.Position; throw new Exception(ex.Message);
}
} }
} }

View File

@@ -18,11 +18,11 @@ namespace Managing.Application.Trading
var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false); var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false);
if (!request.IsForPaperTrading) if (!request.IsForPaperTrading)
{ {
var cancelOrderResult = await exchangeService.CancelOrder(account, request.Ticker); // var cancelOrderResult = await exchangeService.CancelOrder(account, request.Ticker);
if (!cancelOrderResult) // if (!cancelOrderResult)
{ // {
throw new Exception($"Not able to close all orders for {request.Ticker}"); // throw new Exception($"Not able to close all orders for {request.Ticker}");
} // }
} }
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator; var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
@@ -47,63 +47,60 @@ namespace Managing.Application.Trading
: tradingService.GetFee(account, request.IsForPaperTrading); : tradingService.GetFee(account, request.IsForPaperTrading);
var expectedStatus = GetExpectedStatus(request); var expectedStatus = GetExpectedStatus(request);
position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute( // position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(async () => { });
() =>
{
var openPrice = request.IsForPaperTrading || request.Price.HasValue
? request.Price.Value
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
var trade = exchangeService.OpenTrade( var openPrice = request.IsForPaperTrading || request.Price.HasValue
account, ? request.Price.Value
request.Ticker, : exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
request.Direction,
openPrice,
quantity,
request.MoneyManagement.Leverage,
TradeType.Limit,
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date).Result;
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange); // Determine SL/TP Prices
return trade; var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement);
}); var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement);
var trade = await exchangeService.OpenTrade(
account,
request.Ticker,
request.Direction,
openPrice,
quantity,
request.MoneyManagement.Leverage,
TradeType.Limit,
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date,
stopLossPrice: stopLossPrice, // Pass determined SL price
takeProfitPrice: takeProfitPrice); // Pass determined TP price
if (IsOpenTradeHandled(position.Open.Status, account.Exchange) && !request.IgnoreSLTP.GetValueOrDefault()) trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
{ position.Open = trade;
var closeDirection = request.Direction == TradeDirection.Long
? TradeDirection.Short
: TradeDirection.Long;
// Stop loss var closeDirection = request.Direction == TradeDirection.Long
position.StopLoss = exchangeService.BuildEmptyTrade( ? TradeDirection.Short
request.Ticker, : TradeDirection.Long;
RiskHelpers.GetStopLossPrice(request.Direction, position.Open.Price, request.MoneyManagement),
position.Open.Quantity,
closeDirection,
request.MoneyManagement.Leverage,
TradeType.StopLoss,
request.Date,
TradeStatus.PendingOpen);
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee, // Stop loss - Use the determined price
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange); position.StopLoss = exchangeService.BuildEmptyTrade(
request.Ticker,
stopLossPrice, // Use determined SL price
position.Open.Quantity,
closeDirection,
request.MoneyManagement.Leverage,
TradeType.StopLoss,
request.Date,
TradeStatus.PendingOpen);
// Take profit position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
position.TakeProfit1 = exchangeService.BuildEmptyTrade( position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
request.Ticker,
RiskHelpers.GetTakeProfitPrice(request.Direction, position.Open.Price, request.MoneyManagement),
quantity,
closeDirection,
request.MoneyManagement.Leverage,
TradeType.TakeProfit,
request.Date,
TradeStatus.PendingOpen);
position.TakeProfit1.Fee = TradingHelpers.GetFeeAmount(fee, // Take profit - Use the determined price
position.TakeProfit1.Price * position.TakeProfit1.Quantity, account.Exchange); position.TakeProfit1 = exchangeService.BuildEmptyTrade(
} request.Ticker,
takeProfitPrice, // Use determined TP price
quantity,
closeDirection,
request.MoneyManagement.Leverage,
TradeType.TakeProfit,
request.Date,
TradeStatus.PendingOpen);
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange) position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
? position.Status ? position.Status

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Common;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Users; using Managing.Domain.Users;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -42,12 +43,19 @@ public class UserService : IUserService
{ {
var recoveredAddress = _evmManager.VerifySignature(signature, message); var recoveredAddress = _evmManager.VerifySignature(signature, message);
if (!authorizedAddresses.Contains(recoveredAddress)) // Verify message
if (!message.Equals("KaigenTeamXCowchain"))
{ {
_logger.LogWarning($"Address {recoveredAddress} not authorized"); _logger.LogWarning($"Message {message} not starting with KaigenTeamXCowchain");
throw new Exception("Address not authorized"); throw new Exception("Message not good");
} }
// if (!authorizedAddresses.Contains(recoveredAddress))
// {
// _logger.LogWarning($"Address {recoveredAddress} not authorized");
// throw new Exception("Address not authorized");
// }
if (recoveredAddress == null || !recoveredAddress.Equals(address)) if (recoveredAddress == null || !recoveredAddress.Equals(address))
{ {
_logger.LogWarning($"Address {recoveredAddress} not corresponding"); _logger.LogWarning($"Address {recoveredAddress} not corresponding");
@@ -55,50 +63,49 @@ public class UserService : IUserService
} }
// Check if account exist // Check if account exist
var account = await _accountService.GetAccountByKey(recoveredAddress, true, false);
var user = await _userRepository.GetUserByNameAsync(name); var user = await _userRepository.GetUserByNameAsync(name);
if (account != null && user != null) if (user != null)
{ {
if (!user.Name.Equals(name))
throw new Exception("Name not corresponding");
// User and account found // User and account found
user.Accounts = _accountService.GetAccountsByUser(user).ToList(); user.Accounts = _accountService.GetAccountsByUser(user).ToList();
if (!user.Name.Equals(name)) // Check if recoverred address owns the account
throw new Exception("Name not corresponding"); var account = user.Accounts.FirstOrDefault(a => a.Key.Equals(recoveredAddress));
if (account == null)
{
throw new Exception("Account not found");
}
return user; return user;
} }
else else
{ {
// No account and no // First login
account = new Account user = new User
{ {
Name = $"Auth-{new Random().Next(1, 99)}", Name = name
Key = recoveredAddress,
Secret = "",
Exchange = Common.Enums.TradingExchanges.Evm,
Type = Common.Enums.AccountType.Auth,
}; };
if (user != null) await _userRepository.InsertUserAsync(user);
{
_ = await _accountService.CreateAccount(user, account);
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
return user;
}
else
{
// No user found, we create one
// Create user if not existing
user = new User()
{
Name = name,
Accounts = new List<Account> { account },
};
_ = await _accountService.CreateAccount(user, account); // Create embedded account to authenticate user
await _userRepository.InsertUserAsync(user); var account = await _accountService.CreateAccount(user, new Account
} {
Name = $"{name}-embedded",
Key = recoveredAddress,
Exchange = Enums.TradingExchanges.Evm,
Type = Enums.AccountType.Privy
});
user.Accounts = new List<Account>()
{
account
};
} }
return user; return user;

View File

@@ -136,6 +136,10 @@ public static class ApiBootstrap
services.AddSingleton<IWorkerService, WorkerService>(); services.AddSingleton<IWorkerService, WorkerService>();
services.AddTransient<IPrivyService, PrivyService>(); services.AddTransient<IPrivyService, PrivyService>();
// Web3Proxy Configuration
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
// Stream // Stream
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>(); services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
services.AddSingleton<IStreamService, StreamService>(); services.AddSingleton<IStreamService, StreamService>();

View File

@@ -6,14 +6,13 @@ namespace Managing.Domain.Accounts;
public class Account public class Account
{ {
[Required] [Required] public string Name { get; set; }
public string Name { get; set; } [Required] public TradingExchanges Exchange { get; set; }
[Required] [Required] public AccountType Type { get; set; }
public TradingExchanges Exchange { get; set; }
[Required]
public AccountType Type { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string Secret { get; set; } public string Secret { get; set; }
public User User { get; set; } public User User { get; set; }
public List<Balance> Balances { get; set; } public List<Balance> Balances { get; set; }
public bool IsPrivyWallet => Type == AccountType.Privy;
} }

View File

@@ -3,6 +3,7 @@ using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Managing.Domain.Strategies.Base; using Managing.Domain.Strategies.Base;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -29,6 +30,20 @@ public class Backtest
Scenario = scenario; Scenario = scenario;
BotType = botType; BotType = botType;
AccountName = accountName; AccountName = accountName;
WalletBalances = new List<KeyValuePair<DateTime, decimal>>();
StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();
// Initialize start and end dates if candles are provided
if (candles != null && candles.Count > 0)
{
StartDate = candles.Min(c => c.Date);
EndDate = candles.Max(c => c.Date);
}
else
{
StartDate = DateTime.UtcNow.AddDays(-30);
EndDate = DateTime.UtcNow;
}
} }
[Required] public string Id { get; set; } [Required] public string Id { get; set; }
@@ -43,13 +58,15 @@ public class Backtest
[Required] public Timeframe Timeframe { get; } [Required] public Timeframe Timeframe { get; }
[Required] public BotType BotType { get; } [Required] public BotType BotType { get; }
[Required] public string AccountName { get; } [Required] public string AccountName { get; }
[Required] public List<Candle> Candles { get; } [Required] public List<Candle> Candles { get; set; }
[Required] public DateTime StartDate { get; set; }
[Required] public DateTime EndDate { get; set; }
[Required] public PerformanceMetrics Statistics { get; set; } [Required] public PerformanceMetrics Statistics { get; set; }
[Required] public decimal Fees { get; set; } [Required] public decimal Fees { get; set; }
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; } [Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
[Required] public MoneyManagement OptimizedMoneyManagement { get; set; } [Required] public MoneyManagement OptimizedMoneyManagement { get; set; }
[Required] public MoneyManagement MoneyManagement { get; set; } [Required] public MoneyManagement MoneyManagement { get; set; }
[Required] public User User { get; set; }
[Required] public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; } [Required] public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
[Required] public double Score { get; set; } [Required] public double Score { get; set; }

View File

@@ -1,4 +1,6 @@
using static Managing.Common.Enums; using System.Collections.Concurrent;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Bots namespace Managing.Domain.Bots
{ {
@@ -13,6 +15,7 @@ namespace Managing.Domain.Bots
public string Name { get; set; } public string Name { get; set; }
public int Interval { get; set; } public int Interval { get; set; }
public BotStatus Status { get; set; } public BotStatus Status { get; set; }
public User User { get; set; }
private CancellationTokenSource CancellationToken { get; set; } private CancellationTokenSource CancellationToken { get; set; }
public Bot(string name) public Bot(string name)
@@ -22,6 +25,7 @@ namespace Managing.Domain.Bots
Status = BotStatus.Down; Status = BotStatus.Down;
CancellationToken = new CancellationTokenSource(); CancellationToken = new CancellationTokenSource();
ExecutionCount = 0; ExecutionCount = 0;
Interval = 3000;
} }
public virtual void Start() public virtual void Start()

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Domain.MoneyManagements namespace Managing.Domain.MoneyManagements
@@ -18,6 +19,8 @@ namespace Managing.Domain.MoneyManagements
[Required] [Required]
public decimal Leverage { get; set; } public decimal Leverage { get; set; }
public User User { get; set; }
public void FormatPercentage() public void FormatPercentage()
{ {
StopLoss /= 100; StopLoss /= 100;

View File

@@ -1,4 +1,5 @@
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Users;
namespace Managing.Domain.Scenarios namespace Managing.Domain.Scenarios
{ {
@@ -14,6 +15,7 @@ namespace Managing.Domain.Scenarios
public string Name { get; set; } public string Name { get; set; }
public List<Strategy> Strategies { get; set; } public List<Strategy> Strategies { get; set; }
public int? LoopbackPeriod { get; set; } public int? LoopbackPeriod { get; set; }
public User User { get; set; }
public void AddStrategy(Strategy strategy) public void AddStrategy(Strategy strategy)
{ {

View File

@@ -24,7 +24,7 @@ public static class TradingBox
// Ensure limitedCandles is ordered chronologically // Ensure limitedCandles is ordered chronologically
var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList(); var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList();
var loopback = loopbackPeriod ?? 1; var loopback = loopbackPeriod.HasValue && loopbackPeriod > 1 ? loopbackPeriod.Value : 1;
var candleLoopback = orderedCandles.TakeLast(loopback).ToList(); var candleLoopback = orderedCandles.TakeLast(loopback).ToList();
if (!candleLoopback.Any()) if (!candleLoopback.Any())

View File

@@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Managing.Core; using Managing.Core;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Domain.Strategies namespace Managing.Domain.Strategies
@@ -18,9 +19,10 @@ namespace Managing.Domain.Strategies
[Required] public TradingExchanges Exchange { get; set; } [Required] public TradingExchanges Exchange { get; set; }
[Required] public StrategyType StrategyType { get; set; } [Required] public StrategyType StrategyType { get; set; }
[Required] public SignalType SignalType { get; set; } [Required] public SignalType SignalType { get; set; }
public User User { get; set; }
public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date, public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
TradingExchanges exchange, StrategyType strategyType, SignalType signalType) TradingExchanges exchange, StrategyType strategyType, SignalType signalType, User user = null)
{ {
Direction = direction; Direction = direction;
Confidence = confidence; Confidence = confidence;
@@ -30,6 +32,7 @@ namespace Managing.Domain.Strategies
Exchange = exchange; Exchange = exchange;
Status = SignalStatus.WaitingForPosition; Status = SignalStatus.WaitingForPosition;
StrategyType = strategyType; StrategyType = strategyType;
User = user;
Identifier = $"{StrategyType}-{direction}-{ticker}-{candle?.Close}-{date:yyyyMMdd-HHmmss}"; Identifier = $"{StrategyType}-{direction}-{ticker}-{candle?.Close}-{date:yyyyMMdd-HHmmss}";
SignalType = signalType; SignalType = signalType;

View File

@@ -3,6 +3,7 @@ using Managing.Core.FixedSizedQueue;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies.Base; using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Domain.Strategies namespace Managing.Domain.Strategies
@@ -14,6 +15,7 @@ namespace Managing.Domain.Strategies
Name = name; Name = name;
Type = type; Type = type;
SignalType = ScenarioHelpers.GetSignalType(type); SignalType = ScenarioHelpers.GetSignalType(type);
Candles = new FixedSizeQueue<Candle>(500);
} }
public string Name { get; set; } public string Name { get; set; }
@@ -29,6 +31,7 @@ namespace Managing.Domain.Strategies
public int? SmoothPeriods { get; set; } public int? SmoothPeriods { get; set; }
public int? StochPeriods { get; set; } public int? StochPeriods { get; set; }
public int? CyclePeriods { get; set; } public int? CyclePeriods { get; set; }
public User User { get; set; }
public virtual List<Signal> Run() public virtual List<Signal> Run()
{ {

View File

@@ -1,4 +1,5 @@
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -43,6 +44,7 @@ namespace Managing.Domain.Trades
public string Identifier { get; set; } public string Identifier { get; set; }
[Required] [Required]
public PositionInitiator Initiator { get; } public PositionInitiator Initiator { get; }
public User User { get; set; }
public bool IsFinished() public bool IsFinished()
{ {

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb; using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions; using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections; using Managing.Infrastructure.Databases.MongoDb.Collections;
@@ -15,24 +16,54 @@ public class BacktestRepository : IBacktestRepository
_backtestRepository = backtestRepository; _backtestRepository = backtestRepository;
} }
public void DeleteAllBacktests() // User-specific operations
public void InsertBacktestForUser(User user, Backtest backtest)
{ {
_backtestRepository.DropCollection(); backtest.User = user;
_backtestRepository.InsertOne(MongoMappers.Map(backtest));
} }
public void DeleteBacktestById(string id) public IEnumerable<Backtest> GetBacktestsByUser(User user)
{ {
_backtestRepository.DeleteById(id); var backtests = _backtestRepository.AsQueryable()
} .Where(b => b.User != null && b.User.Name == user.Name)
.ToList();
public IEnumerable<Backtest> GetBacktests()
{
var backtests = _backtestRepository.FindAll();
return backtests.Select(b => MongoMappers.Map(b)); return backtests.Select(b => MongoMappers.Map(b));
} }
public void InsertBacktest(Backtest backtest) public Backtest GetBacktestByIdForUser(User user, string id)
{ {
_backtestRepository.InsertOne(MongoMappers.Map(backtest)); var backtest = _backtestRepository.FindById(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.FindById(id);
if (backtest != null && backtest.User != null && backtest.User.Name == user.Name)
{
_backtestRepository.DeleteById(id);
}
}
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());
}
} }
} }

View File

@@ -45,6 +45,29 @@ public class CandleRepository : ICandleRepository
return results; return results;
} }
public async Task<IList<Candle>> GetCandles(
TradingExchanges exchange,
Ticker ticker,
Timeframe timeframe,
DateTime start,
DateTime end)
{
var results = await _influxDbRepository.QueryAsync(async query =>
{
var flux = $"from(bucket:\"{_priceBucket}\") " +
$"|> range(start: {start:s}Z, stop: {end:s}Z) " +
$"|> filter(fn: (r) => r[\"exchange\"] == \"{exchange}\")" +
$"|> filter(fn: (r) => r[\"ticker\"] == \"{ticker}\")" +
$"|> filter(fn: (r) => r[\"timeframe\"] == \"{timeframe}\")" +
$"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";
var prices = await query.QueryAsync<PriceDto>(flux, _influxDbRepository.Organization);
return prices.Select(price => PriceHelpers.Map(price)).ToList();
});
return results;
}
public async Task<IList<Ticker>> GetTickersAsync( public async Task<IList<Ticker>> GetTickersAsync(
TradingExchanges exchange, TradingExchanges exchange,
Timeframe timeframe, Timeframe timeframe,

View File

@@ -1,5 +1,6 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes; using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations; using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Exilion.TradingAtomics;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections namespace Managing.Infrastructure.Databases.MongoDb.Collections
@@ -19,8 +20,13 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public RiskLevel RiskLevel { get; set; } public RiskLevel RiskLevel { get; set; }
public string AccountName { get; set; } public string AccountName { get; set; }
public List<CandleDto> Candles { get; set; } public List<CandleDto> Candles { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public BotType BotType { get; set; } public BotType BotType { get; set; }
public MoneyManagementDto MoneyManagement { get; internal set; } public MoneyManagementDto MoneyManagement { get; internal set; }
public MoneyManagementDto OptimizedMoneyManagement { get; internal set; } public MoneyManagementDto OptimizedMoneyManagement { get; internal set; }
public UserDto User { get; set; }
public PerformanceMetrics Statistics { get; set; }
public double Score { get; set; }
} }
} }

View File

@@ -14,5 +14,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public decimal TakeProfit { get; set; } public decimal TakeProfit { get; set; }
public decimal Leverage { get; set; } public decimal Leverage { get; set; }
public string Name { get; internal set; } public string Name { get; internal set; }
public UserDto User { get; set; }
} }
} }

View File

@@ -23,5 +23,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public string AccountName { get; set; } public string AccountName { get; set; }
public MoneyManagementDto MoneyManagement { get; set; } public MoneyManagementDto MoneyManagement { get; set; }
public PositionInitiator Initiator { get; set; } public PositionInitiator Initiator { get; set; }
public UserDto User { get; set; }
} }
} }

View File

@@ -9,5 +9,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public string Name { get; set; } public string Name { get; set; }
public List<StrategyDto> Strategies { get; set; } public List<StrategyDto> Strategies { get; set; }
public int LoopbackPeriod { get; set; } public int LoopbackPeriod { get; set; }
public UserDto User { get; set; }
} }
} }

View File

@@ -17,5 +17,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public Timeframe Timeframe { get; set; } public Timeframe Timeframe { get; set; }
public StrategyType Type { get; set; } public StrategyType Type { get; set; }
public SignalType SignalType { get; set; } public SignalType SignalType { get; set; }
public UserDto User { get; set; }
} }
} }

View File

@@ -10,6 +10,7 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public StrategyType Type { get; set; } public StrategyType Type { get; set; }
public Timeframe Timeframe { get; set; } public Timeframe Timeframe { get; set; }
public string Name { get; set; } public string Name { get; set; }
public int MinimumHistory { get; set; }
public int? Period { get; set; } public int? Period { get; set; }
public int? FastPeriods { get; set; } public int? FastPeriods { get; set; }
public int? SlowPeriods { get; set; } public int? SlowPeriods { get; set; }
@@ -19,5 +20,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public int? SmoothPeriods { get; set; } public int? SmoothPeriods { get; set; }
public int? CyclePeriods { get; set; } public int? CyclePeriods { get; set; }
public SignalType SignalType { get; set; } public SignalType SignalType { get; set; }
public UserDto User { get; set; }
} }
} }

View File

@@ -1,4 +1,5 @@
using Managing.Domain.Accounts; using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
@@ -12,6 +13,8 @@ using Managing.Domain.Workers;
using Managing.Domain.Workflows.Synthetics; using Managing.Domain.Workflows.Synthetics;
using Managing.Infrastructure.Databases.MongoDb.Collections; using Managing.Infrastructure.Databases.MongoDb.Collections;
using static Managing.Common.Enums; using static Managing.Common.Enums;
using MongoDB.Bson;
using Managing.Domain.Shared.Helpers;
namespace Managing.Infrastructure.Databases.MongoDb; namespace Managing.Infrastructure.Databases.MongoDb;
@@ -126,47 +129,63 @@ public static class MongoMappers
internal static Backtest Map(BacktestDto b) internal static Backtest Map(BacktestDto b)
{ {
return new Backtest( if (b == null)
ticker: b.Ticker, return null;
scenario: b.Scenario,
positions: b.Positions.ConvertAll(bPosition => Map(bPosition)), var bTest = new Backtest(
signals: b.Signals != null ? b.Signals.ConvertAll(bSignal => Map(bSignal)) : null, b.Ticker,
timeframe: b.Timeframe, b.Scenario,
candles: b.Candles.ConvertAll(bCandle => Map(bCandle)), b.Positions?.Select(p => Map(p)).ToList() ?? new List<Position>(),
accountName: b.AccountName, b.Signals?.Select(s => Map(s)).ToList() ?? new List<Signal>(),
botType: b.BotType) b.Timeframe,
b.Candles?.Select(c => Map(c)).ToList() ?? new List<Candle>(),
b.BotType,
b.AccountName)
{ {
Id = b.Id.ToString(),
FinalPnl = b.FinalPnl, FinalPnl = b.FinalPnl,
WinRate = b.WinRate, WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage, GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage, HodlPercentage = b.HodlPercentage,
Id = b.Id.ToString(),
MoneyManagement = Map(b.MoneyManagement), MoneyManagement = Map(b.MoneyManagement),
OptimizedMoneyManagement = Map(b.OptimizedMoneyManagement) OptimizedMoneyManagement = Map(b.OptimizedMoneyManagement),
User = b.User != null ? Map(b.User) : null,
Statistics = b.Statistics,
StartDate = b.StartDate,
EndDate = b.EndDate,
Score = b.Score
}; };
return bTest;
} }
internal static BacktestDto Map(Backtest result) internal static BacktestDto Map(Backtest result)
{ {
var backtest = new BacktestDto if (result == null)
return null;
return new BacktestDto
{ {
Id = (!string.IsNullOrEmpty(result.Id)) ? ObjectId.Parse(result.Id) : ObjectId.GenerateNewId(),
FinalPnl = result.FinalPnl, FinalPnl = result.FinalPnl,
WinRate = result.WinRate, WinRate = result.WinRate,
GrowthPercentage = result.GrowthPercentage, GrowthPercentage = result.GrowthPercentage,
HodlPercentage = result.HodlPercentage, HodlPercentage = result.HodlPercentage,
Candles = Map(result.Candles),
Positions = Map(result.Positions), Positions = Map(result.Positions),
Signals = result.Signals.Select(s => Map(s)).ToList(),
Ticker = result.Ticker,
Scenario = result.Scenario,
AccountName = result.AccountName, AccountName = result.AccountName,
BotType = result.BotType, BotType = result.BotType,
Timeframe = result.Timeframe,
MoneyManagement = Map(result.MoneyManagement), MoneyManagement = Map(result.MoneyManagement),
OptimizedMoneyManagement = Map(result.OptimizedMoneyManagement), OptimizedMoneyManagement = Map(result.OptimizedMoneyManagement),
User = result.User != null ? Map(result.User) : null,
Statistics = result.Statistics,
StartDate = result.StartDate,
EndDate = result.EndDate,
Score = result.Score
}; };
backtest.Timeframe = result.Timeframe;
backtest.Ticker = result.Ticker;
backtest.Scenario = result.Scenario;
return backtest;
} }
#endregion #endregion
@@ -237,7 +256,8 @@ public static class MongoMappers
AccountName = position.AccountName, AccountName = position.AccountName,
MoneyManagement = Map(position.MoneyManagement), MoneyManagement = Map(position.MoneyManagement),
Initiator = position.Initiator, Initiator = position.Initiator,
Ticker = position.Ticker Ticker = position.Ticker,
User = position.User != null ? Map(position.User) : null
}; };
if (position.StopLoss != null) if (position.StopLoss != null)
@@ -284,7 +304,8 @@ public static class MongoMappers
ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss }, ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss },
Status = dto.Status, Status = dto.Status,
SignalIdentifier = dto.SignalIdentifier, SignalIdentifier = dto.SignalIdentifier,
Identifier = dto.Identifier Identifier = dto.Identifier,
User = dto.User != null ? Map(dto.User) : null
}; };
if (dto.StopLoss != null) if (dto.StopLoss != null)
@@ -327,26 +348,42 @@ public static class MongoMappers
{ {
return new SignalDto return new SignalDto
{ {
Identifier = signal.Identifier,
Direction = signal.Direction, Direction = signal.Direction,
Candle = Map(signal.Candle),
Confidence = signal.Confidence, Confidence = signal.Confidence,
Date = signal.Date, Date = signal.Date,
Candle = Map(signal.Candle),
Identifier = signal.Identifier,
Ticker = signal.Ticker, Ticker = signal.Ticker,
Status = signal.Status, Status = signal.Status,
Timeframe = signal.Timeframe, Timeframe = signal.Timeframe,
Type = signal.StrategyType Type = signal.StrategyType,
User = signal.User != null ? Map(signal.User) : null
}; };
} }
internal static Signal Map(SignalDto bSignal) internal static Signal Map(SignalDto bSignal)
{ {
return new Signal(ticker: bSignal.Ticker, direction: bSignal.Direction, confidence: bSignal.Confidence, var candle = Map(bSignal.Candle);
candle: Map(bSignal.Candle), date: bSignal.Date, exchange: default, var signal = new Signal(
strategyType: bSignal.Type, signalType: bSignal.SignalType) 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.User != null ? Map(bSignal.User) : null)
{ {
Status = bSignal.Status Status = bSignal.Status
}; };
if (bSignal.User != null)
{
signal.User = Map(bSignal.User);
}
return signal;
} }
#endregion #endregion
@@ -355,11 +392,15 @@ public static class MongoMappers
public static ScenarioDto Map(Scenario scenario) public static ScenarioDto Map(Scenario scenario)
{ {
return new ScenarioDto() if (scenario == null)
return null;
return new ScenarioDto
{ {
Name = scenario.Name, Name = scenario.Name,
Strategies = Map(scenario.Strategies), Strategies = Map(scenario.Strategies),
LoopbackPeriod = scenario.LoopbackPeriod ?? 1 LoopbackPeriod = scenario.LoopbackPeriod ?? 1,
User = scenario.User != null ? Map(scenario.User) : null
}; };
} }
@@ -370,12 +411,15 @@ public static class MongoMappers
internal static Scenario Map(ScenarioDto d) internal static Scenario Map(ScenarioDto d)
{ {
return new Scenario(d.Name) if (d == null)
return null;
var scenario = new Scenario(d.Name, d.LoopbackPeriod)
{ {
Name = d.Name, Strategies = d.Strategies.Select(s => Map(s)).ToList(),
Strategies = Map(d.Strategies).ToList(), User = d.User != null ? Map(d.User) : null
LoopbackPeriod = d.LoopbackPeriod > 0 ? d.LoopbackPeriod : 1
}; };
return scenario;
} }
private static List<StrategyDto> Map(List<Strategy> strategies) private static List<StrategyDto> Map(List<Strategy> strategies)
@@ -385,8 +429,13 @@ public static class MongoMappers
internal static Strategy Map(StrategyDto strategyDto) internal static Strategy Map(StrategyDto strategyDto)
{ {
return new Strategy(name: strategyDto.Name, type: strategyDto.Type) if (strategyDto == null)
return null;
return new Strategy(strategyDto.Name, strategyDto.Type)
{ {
SignalType = strategyDto.SignalType,
MinimumHistory = strategyDto.MinimumHistory,
Period = strategyDto.Period, Period = strategyDto.Period,
FastPeriods = strategyDto.FastPeriods, FastPeriods = strategyDto.FastPeriods,
SlowPeriods = strategyDto.SlowPeriods, SlowPeriods = strategyDto.SlowPeriods,
@@ -395,64 +444,31 @@ public static class MongoMappers
SmoothPeriods = strategyDto.SmoothPeriods, SmoothPeriods = strategyDto.SmoothPeriods,
StochPeriods = strategyDto.StochPeriods, StochPeriods = strategyDto.StochPeriods,
CyclePeriods = strategyDto.CyclePeriods, CyclePeriods = strategyDto.CyclePeriods,
SignalType = strategyDto.SignalType User = strategyDto.User != null ? Map(strategyDto.User) : null
}; };
} }
internal static StrategyDto Map(Strategy strategy) internal static StrategyDto Map(Strategy strategy)
{ {
var dto = new StrategyDto if (strategy == null)
return null;
return new StrategyDto
{ {
Type = strategy.Type,
Name = strategy.Name, Name = strategy.Name,
Type = strategy.Type,
SignalType = strategy.SignalType, SignalType = strategy.SignalType,
CyclePeriods = strategy.CyclePeriods, MinimumHistory = strategy.MinimumHistory,
FastPeriods = strategy.FastPeriods,
Multiplier = strategy.Multiplier,
Period = strategy.Period, Period = strategy.Period,
SignalPeriods = strategy.SignalPeriods, FastPeriods = strategy.FastPeriods,
SlowPeriods = strategy.SlowPeriods, SlowPeriods = strategy.SlowPeriods,
SignalPeriods = strategy.SignalPeriods,
Multiplier = strategy.Multiplier,
SmoothPeriods = strategy.SmoothPeriods, SmoothPeriods = strategy.SmoothPeriods,
StochPeriods = strategy.StochPeriods StochPeriods = strategy.StochPeriods,
CyclePeriods = strategy.CyclePeriods,
User = strategy.User != null ? Map(strategy.User) : null
}; };
// switch (strategy.Type)
// {
// case StrategyType.RsiDivergenceConfirm:
// case StrategyType.RsiDivergence:
// case StrategyType.EmaCross:
// case StrategyType.EmaTrend:
// case StrategyType.StDev:
// dto.Period = strategy.Period;
// break;
// case StrategyType.MacdCross:
// dto.SlowPeriods = strategy.SlowPeriods;
// dto.FastPeriods = strategy.FastPeriods;
// dto.SignalPeriods = strategy.SignalPeriods;
// break;
// case StrategyType.ThreeWhiteSoldiers:
// break;
// case StrategyType.ChandelierExit:
// case StrategyType.SuperTrend:
// dto.Period = strategy.Period;
// dto.Multiplier = strategy.Multiplier;
// break;
// case StrategyType.StochRsiTrend:
// dto.Period = strategy.Period;
// dto.StochPeriods = strategy.StochPeriods;
// dto.SignalPeriods = strategy.SignalPeriods;
// dto.SmoothPeriods = strategy.SmoothPeriods;
// break;
// case StrategyType.Stc:
// dto.SlowPeriods = strategy.SlowPeriods;
// dto.FastPeriods = strategy.FastPeriods;
// dto.CyclePeriods = strategy.CyclePeriods;
// break;
// default:
// break;
// }
return dto;
} }
internal static IEnumerable<Strategy> Map(IEnumerable<StrategyDto> strategies) internal static IEnumerable<Strategy> Map(IEnumerable<StrategyDto> strategies)
@@ -474,7 +490,8 @@ public static class MongoMappers
StopLoss = request.StopLoss, StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit, TakeProfit = request.TakeProfit,
Leverage = request.Leverage, Leverage = request.Leverage,
Name = request.Name Name = request.Name,
User = request.User != null ? Map(request.User) : null
}; };
} }
@@ -490,7 +507,8 @@ public static class MongoMappers
StopLoss = request.StopLoss, StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit, TakeProfit = request.TakeProfit,
Leverage = request.Leverage, Leverage = request.Leverage,
Name = request.Name Name = request.Name,
User = request.User != null ? Map(request.User) : null
}; };
} }

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb; using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions; using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections; using Managing.Infrastructure.Databases.MongoDb.Collections;
@@ -50,4 +51,49 @@ public class SettingsRepository : ISettingsRepository
dto.Id = mm.Id; dto.Id = mm.Id;
_moneyManagementRepository.Update(dto); _moneyManagementRepository.Update(dto);
} }
// User-specific implementations
public async Task<MoneyManagement> 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<MoneyManagement> 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());
}
}
} }

View File

@@ -2,6 +2,7 @@
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb; using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions; using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections; using Managing.Infrastructure.Databases.MongoDb.Collections;
@@ -100,23 +101,98 @@ public class TradingRepository : ITradingRepository
public void InsertPosition(Position position) public void InsertPosition(Position position)
{ {
_positionRepository.InsertOne(MongoMappers.Map(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) public void InsertScenario(Scenario scenario)
{ {
_scenarioRepository.CreateIndex(nameof(Scenario.Name)); // Check if scenario already exists for the same user
_scenarioRepository.InsertOne(MongoMappers.Map(scenario)); 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<StrategyDto>();
foreach (var strategy in scenario.Strategies)
{
var dto = _strategyRepository.FindOne(s => s.Name == strategy.Name);
strategyDtos.Add(dto);
}
var scenarioDto = new ScenarioDto
{
Name = scenario.Name,
Strategies = strategyDtos,
User = scenario.User != null ? MongoMappers.Map(scenario.User) : null
};
_scenarioRepository.InsertOne(scenarioDto);
} }
public void InsertSignal(Signal signal) public void InsertSignal(Signal signal)
{ {
_signalRepository.InsertOne(MongoMappers.Map(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(Strategy strategy) public void InsertStrategy(Strategy strategy)
{ {
_strategyRepository.InsertOne(MongoMappers.Map(strategy)); // Check if strategy already exists for the same user
var existingStrategy = _strategyRepository.FindOne(s =>
s.Name == strategy.Name &&
(strategy.User == null || (s.User != null && s.User.Name == strategy.User.Name)));
if (existingStrategy != null)
{
throw new InvalidOperationException(
$"Strategy with name '{strategy.Name}' already exists for user '{strategy.User?.Name}'");
}
var dto = MongoMappers.Map(strategy);
_strategyRepository.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) public void UpdatePosition(Position position)
@@ -133,9 +209,41 @@ public class TradingRepository : ITradingRepository
return MongoMappers.Map(fee); return MongoMappers.Map(fee);
} }
public void InsertFee(Fee fee) public IEnumerable<Signal> GetSignalsByUser(User user)
{ {
_feeRepository.InsertOne(MongoMappers.Map(fee)); IEnumerable<SignalDto> 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) public void UpdateFee(Fee fee)

View File

@@ -25,4 +25,19 @@ public class UserRepository : IUserRepository
{ {
await _userRepository.InsertOneAsync(MongoMappers.Map(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.Name = user.Name;
_userRepository.Update(dto);
}
catch (Exception e)
{
Console.WriteLine(e);
throw new Exception("Cannot update user");
}
}
} }

View File

@@ -0,0 +1,8 @@
namespace Managing.Infrastructure.Evm.Models.Privy;
public class PrivyApproveTokenResponse
{
public bool Success { get; set; }
public string? Hash { get; set; }
public string? Error { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace Managing.Infrastructure.Evm.Models.Privy;
public class PrivyInitAddressResponse
{
public bool Success { get; set; }
public string? UsdcHash { get; set; }
public string? OrderVaultHash { get; set; }
public string? Error { get; set; }
}

View File

@@ -22,7 +22,9 @@ public interface IExchangeProcessor
bool reduceOnly = false, bool reduceOnly = false,
bool isForPaperTrading = false, bool isForPaperTrading = false,
DateTime? currentDate = null, DateTime? currentDate = null,
bool ioc = true); bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false); Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false); Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
decimal GetPrice(Account account, Ticker ticker, DateTime date); decimal GetPrice(Account account, Ticker ticker, DateTime date);

View File

@@ -37,10 +37,12 @@ namespace Managing.Infrastructure.Exchanges
bool reduceOnly = false, bool reduceOnly = false,
bool isForPaperTrading = false, bool isForPaperTrading = false,
DateTime? currentDate = null, DateTime? currentDate = null,
bool ioc = true) bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
{ {
_logger.LogInformation( _logger.LogInformation(
$"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage}"); $"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage} - SL: {stopLossPrice} - TP: {takeProfitPrice}");
if (isForPaperTrading) if (isForPaperTrading)
{ {
@@ -50,7 +52,7 @@ namespace Managing.Infrastructure.Exchanges
var processor = GetProcessor(account); var processor = GetProcessor(account);
return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType, return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType,
reduceOnly, isForPaperTrading, currentDate, ioc); reduceOnly, isForPaperTrading, currentDate, ioc, stopLossPrice, takeProfitPrice);
} }
private IExchangeProcessor GetProcessor(Account account) private IExchangeProcessor GetProcessor(Account account)
@@ -205,6 +207,13 @@ namespace Managing.Infrastructure.Exchanges
return candlesFromRepo.ToList(); return candlesFromRepo.ToList();
} }
public async Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
Timeframe timeframe, DateTime endDate)
{
var candlesFromRepo = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate);
return candlesFromRepo.ToList();
}
public async Task<decimal> GetBalance(Account account, bool isForPaperTrading = false) public async Task<decimal> GetBalance(Account account, bool isForPaperTrading = false)
{ {
if (isForPaperTrading) if (isForPaperTrading)

View File

@@ -22,7 +22,20 @@ namespace Managing.Infrastructure.Exchanges.Exchanges
public abstract Task<Trade> GetTrade(Account account, string order, Ticker ticker); public abstract Task<Trade> GetTrade(Account account, string order, Ticker ticker);
public abstract Task<List<Trade>> GetTrades(Account account, Ticker ticker); public abstract Task<List<Trade>> GetTrades(Account account, Ticker ticker);
public abstract decimal GetVolume(Account account, Ticker ticker); public abstract decimal GetVolume(Account account, Ticker ticker);
public abstract Task<Trade> OpenTrade(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = null, Enums.TradeType tradeType = Enums.TradeType.Limit, bool reduceOnly = false, bool isForPaperTrading = false, DateTime? currentDate = null, bool ioc = true); public abstract Task<Trade> 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);
public abstract Orderbook GetOrderbook(Account account, Ticker ticker); public abstract Orderbook GetOrderbook(Account account, Ticker ticker);
public abstract Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false); public abstract Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker); public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker);

View File

@@ -27,7 +27,8 @@ public class BinanceProcessor : BaseProcessor
public override async Task<bool> CancelOrder(Account account, Ticker ticker) public override async Task<bool> CancelOrder(Account account, Ticker ticker)
{ {
var binanceResult = await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); var binanceResult =
await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
return binanceResult.Success; return binanceResult.Success;
} }
@@ -41,6 +42,7 @@ public class BinanceProcessor : BaseProcessor
{ {
balance += item.AvailableBalance; balance += item.AvailableBalance;
} }
return balance; return balance;
} }
@@ -54,12 +56,15 @@ public class BinanceProcessor : BaseProcessor
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
Timeframe timeframe)
{ {
var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync(BinanceHelpers.ToBinanceTicker(ticker), var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync(
BinanceHelpers.Map(timeframe), startDate); BinanceHelpers.ToBinanceTicker(ticker),
BinanceHelpers.Map(timeframe), startDate);
return (List<Candle>)binanceCandles.Data.Select(binanceKline => BinanceHelpers.Map(binanceKline, ticker, account.Exchange)); return (List<Candle>)binanceCandles.Data.Select(binanceKline =>
BinanceHelpers.Map(binanceKline, ticker, account.Exchange));
} }
public override decimal GetFee(Account account, bool isForPaperTrading = false) public override decimal GetFee(Account account, bool isForPaperTrading = false)
@@ -79,7 +84,8 @@ public class BinanceProcessor : BaseProcessor
public override decimal GetPrice(Account account, Ticker ticker, DateTime date) public override decimal GetPrice(Account account, Ticker ticker, DateTime date)
{ {
var binancePrice = _binanceClient.UsdFuturesApi.ExchangeData.GetPriceAsync(BinanceHelpers.ToBinanceTicker(ticker)).Result.Data; var binancePrice = _binanceClient.UsdFuturesApi.ExchangeData
.GetPriceAsync(BinanceHelpers.ToBinanceTicker(ticker)).Result.Data;
return binancePrice.Price; return binancePrice.Price;
} }
@@ -90,7 +96,9 @@ public class BinanceProcessor : BaseProcessor
public override async Task<Trade> GetTrade(Account account, string order, Ticker ticker) public override async Task<Trade> GetTrade(Account account, string order, Ticker ticker)
{ {
var binanceOrder = await _binanceClient.UsdFuturesApi.Trading.GetOrderAsync(BinanceHelpers.ToBinanceTicker(ticker), origClientOrderId: order); var binanceOrder =
await _binanceClient.UsdFuturesApi.Trading.GetOrderAsync(BinanceHelpers.ToBinanceTicker(ticker),
origClientOrderId: order);
return BinanceHelpers.Map(binanceOrder.Data); return BinanceHelpers.Map(binanceOrder.Data);
} }
@@ -107,7 +115,7 @@ public class BinanceProcessor : BaseProcessor
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker) public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
{ {
var binanceOrder = var binanceOrder =
await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
return (List<Trade>)binanceOrder.Data.Select(o => BinanceHelpers.Map(o)); return (List<Trade>)binanceOrder.Data.Select(o => BinanceHelpers.Map(o));
} }
@@ -122,9 +130,14 @@ public class BinanceProcessor : BaseProcessor
_binanceClient = new BinanceRestClient((options) => { options.ApiCredentials = credentials; }); _binanceClient = new BinanceRestClient((options) => { options.ApiCredentials = credentials; });
} }
public override async Task<Trade> 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) public override async Task<Trade> 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, "", ""); var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price,
leverage, "", "");
trade.SetQuantity(quantity, GetQuantityPrecision(account, ticker)); trade.SetQuantity(quantity, GetQuantityPrecision(account, ticker));
trade.SetPrice(price, GetPricePrecision(account, ticker)); trade.SetPrice(price, GetPricePrecision(account, ticker));
@@ -158,6 +171,7 @@ public class BinanceProcessor : BaseProcessor
trade.SetExchangeOrderId(""); trade.SetExchangeOrderId("");
trade.SetMessage(""); trade.SetMessage("");
} }
return trade; return trade;
} }
@@ -172,7 +186,8 @@ public class BinanceProcessor : BaseProcessor
private int GetQuantityPrecision(Account account, Ticker ticker) private int GetQuantityPrecision(Account account, Ticker ticker)
{ {
var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data; var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data;
var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker)).QuantityPrecision; var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker))
.QuantityPrecision;
return Convert.ToInt32(precision); return Convert.ToInt32(precision);
} }
} }

View File

@@ -67,7 +67,8 @@ public class EvmProcessor : BaseProcessor
return _evmManager.GetCandle(SubgraphProvider.Gbc, ticker).Result; return _evmManager.GetCandle(SubgraphProvider.Gbc, ticker).Result;
} }
public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval) public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
Timeframe interval)
{ {
return await _evmManager.GetCandles(SubgraphProvider.Gbc, ticker, startDate, interval); return await _evmManager.GetCandles(SubgraphProvider.Gbc, ticker, startDate, interval);
} }
@@ -119,7 +120,9 @@ public class EvmProcessor : BaseProcessor
bool reduceOnly = false, bool reduceOnly = false,
bool isForPaperTrading = false, bool isForPaperTrading = false,
DateTime? currentDate = null, DateTime? currentDate = null,
bool ioc = true) bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
{ {
Trade trade; Trade trade;
if (reduceOnly) if (reduceOnly)
@@ -128,7 +131,8 @@ public class EvmProcessor : BaseProcessor
or TradeType.StopLoss) or TradeType.StopLoss)
{ {
// If trade type is TP or SL we create DecreaseOrder // If trade type is TP or SL we create DecreaseOrder
trade = await _evmManager.DecreaseOrder(account, tradeType, ticker, direction, price, quantity, leverage); trade = await _evmManager.DecreaseOrder(account, tradeType, ticker, direction, price, quantity,
leverage);
} }
else else
{ {
@@ -140,7 +144,8 @@ public class EvmProcessor : BaseProcessor
} }
else else
{ {
trade = await _evmManager.IncreasePosition(account, ticker, direction, price, quantity, leverage); trade = await _evmManager.IncreasePosition(account, ticker, direction, price, quantity, leverage,
stopLossPrice, takeProfitPrice);
} }
return trade; return trade;
@@ -149,11 +154,9 @@ public class EvmProcessor : BaseProcessor
public override async Task<List<Trade>> GetOrders(Account account, Ticker ticker) public override async Task<List<Trade>> GetOrders(Account account, Ticker ticker)
{ {
return await _evmManager.GetOrders(account, ticker); return await _evmManager.GetOrders(account, ticker);
} }
#region Not implemented #region Not implemented
public override void LoadClient(Account account) public override void LoadClient(Account account)

View File

@@ -53,6 +53,7 @@ public class FtxProcessor : BaseProcessor
{ {
balance += item.UsdValue; balance += item.UsdValue;
} }
return balance; return balance;
} }
@@ -61,7 +62,7 @@ public class FtxProcessor : BaseProcessor
LoadClient(account); LoadClient(account);
var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
if (ftxKlines != null && ftxKlines.Any()) if (ftxKlines != null && ftxKlines.Any())
{ {
var lastCandle = ftxKlines.ToList().LastOrDefault(); var lastCandle = ftxKlines.ToList().LastOrDefault();
@@ -71,16 +72,18 @@ public class FtxProcessor : BaseProcessor
return null; return null;
} }
public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
Timeframe timeframe)
{ {
LoadClient(account); LoadClient(account);
var candles = new List<Candle>(); var candles = new List<Candle>();
var ftxCandles = await _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), var ftxCandles = await _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
FtxHelpers.Map(timeframe), startDate); FtxHelpers.Map(timeframe), startDate);
if (ftxCandles.Success) if (ftxCandles.Success)
candles.AddRange(ftxCandles.Data.SkipLast(1).Select(ftxKline => FtxHelpers.Map(ftxKline, ticker, account.Exchange, timeframe))); candles.AddRange(ftxCandles.Data.SkipLast(1)
.Select(ftxKline => FtxHelpers.Map(ftxKline, ticker, account.Exchange, timeframe)));
return candles; return candles;
} }
@@ -96,11 +99,12 @@ public class FtxProcessor : BaseProcessor
{ {
LoadClient(account); LoadClient(account);
var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
if (ftxKlines != null && ftxKlines.Any()) if (ftxKlines != null && ftxKlines.Any())
{ {
return ftxKlines.ToList().LastOrDefault().ClosePrice; return ftxKlines.ToList().LastOrDefault().ClosePrice;
} }
return 0; return 0;
} }
@@ -126,29 +130,36 @@ public class FtxProcessor : BaseProcessor
public override decimal GetVolume(Account account, Ticker ticker) public override decimal GetVolume(Account account, Ticker ticker)
{ {
var futureStats = _ftxClient.TradeApi.ExchangeData.GetFutureStatsAsync(FtxHelpers.ToFtxTicker(ticker)).Result.Data; var futureStats = _ftxClient.TradeApi.ExchangeData.GetFutureStatsAsync(FtxHelpers.ToFtxTicker(ticker)).Result
.Data;
return futureStats.Volume; return futureStats.Volume;
} }
public override async Task<Trade> 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) public override async Task<Trade> 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); LoadClient(account);
var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, leverage, "", ""); var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price,
leverage, "", "");
trade.SetQuantity(quantity, 6); trade.SetQuantity(quantity, 6);
Trade ftxOrder; Trade ftxOrder;
if (tradeType == TradeType.StopLoss || tradeType == TradeType.TakeProfitLimit || tradeType == TradeType.StopMarket) if (tradeType == TradeType.StopLoss || tradeType == TradeType.TakeProfitLimit ||
tradeType == TradeType.StopMarket)
{ {
var ftxTriggerOrderType = FtxHelpers.FtxTriggerOrderTypeMap(tradeType); var ftxTriggerOrderType = FtxHelpers.FtxTriggerOrderTypeMap(tradeType);
var ftxResult = await _ftxClient.TradeApi.Trading.PlaceTriggerOrderAsync(FtxHelpers.ToFtxTicker(ticker), var ftxResult = await _ftxClient.TradeApi.Trading.PlaceTriggerOrderAsync(FtxHelpers.ToFtxTicker(ticker),
direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy, direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy,
ftxTriggerOrderType, ftxTriggerOrderType,
triggerPrice: price, triggerPrice: price,
reduceOnly: true, reduceOnly: true,
retryUntilFilled: false, retryUntilFilled: false,
quantity: quantity); quantity: quantity);
_logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult)); _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult));
ftxOrder = FtxHelpers.Map(ftxResult, leverage); ftxOrder = FtxHelpers.Map(ftxResult, leverage);
} }
@@ -177,7 +188,8 @@ public class FtxProcessor : BaseProcessor
public override Orderbook GetOrderbook(Account account, Ticker ticker) public override Orderbook GetOrderbook(Account account, Ticker ticker)
{ {
LoadClient(account); LoadClient(account);
var ftxOrderBook = _ftxClient.TradeApi.ExchangeData.GetOrderBookAsync(FtxHelpers.ToFtxTicker(ticker), 100).Result; var ftxOrderBook = _ftxClient.TradeApi.ExchangeData.GetOrderBookAsync(FtxHelpers.ToFtxTicker(ticker), 100)
.Result;
return FtxHelpers.Map(ftxOrderBook); return FtxHelpers.Map(ftxOrderBook);
} }

View File

@@ -23,6 +23,7 @@ public class KrakenProcessor : BaseProcessor
{ {
_logger = logger; _logger = logger;
} }
public override Task<bool> CancelOrder(Account account, Ticker ticker) public override Task<bool> CancelOrder(Account account, Ticker ticker)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -48,7 +49,8 @@ public class KrakenProcessor : BaseProcessor
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval) public override Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
Timeframe interval)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -72,7 +74,7 @@ public class KrakenProcessor : BaseProcessor
{ {
LoadClient(account); LoadClient(account);
var krakenKline = _krakenClient.SpotApi.ExchangeData.GetKlinesAsync(ticker.ToString(), var krakenKline = _krakenClient.SpotApi.ExchangeData.GetKlinesAsync(ticker.ToString(),
Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0]; Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0];
return (krakenKline.HighPrice + krakenKline.ClosePrice) / 2; return (krakenKline.HighPrice + krakenKline.ClosePrice) / 2;
} }
@@ -120,10 +122,15 @@ public class KrakenProcessor : BaseProcessor
_krakenClient = new KrakenRestClient((options) => { options.ApiCredentials = krakenConfig.ApiCredentials; }); _krakenClient = new KrakenRestClient((options) => { options.ApiCredentials = krakenConfig.ApiCredentials; });
} }
public override async Task<Trade> 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) public override async Task<Trade> 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); LoadClient(account);
var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, leverage, "", ""); var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price,
leverage, "", "");
trade.SetQuantity(quantity, 6); trade.SetQuantity(quantity, 6);
trade.SetPrice(price, 1); trade.SetPrice(price, 1);

View File

@@ -9,6 +9,7 @@ using Managing.Application.Trading.Commands;
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Managing.Common; using Managing.Common;
using Managing.Core; using Managing.Core;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -262,9 +263,12 @@ namespace Managing.Infrastructure.Messengers.Discord
var accountService = (IAccountService)_services.GetService(typeof(IAccountService)); var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService)); var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
// Create default user for Discord bot operations
var defaultUser = new Domain.Users.User { Name = "DiscordBot" };
var tradeCommand = new OpenPositionRequest( var tradeCommand = new OpenPositionRequest(
accountName, accountName,
await moneyManagementService.GetMoneyMangement(moneyManagement), await moneyManagementService.GetMoneyMangement(defaultUser, moneyManagement),
direction, direction,
ticker, ticker,
initiator, initiator,
@@ -403,8 +407,21 @@ namespace Managing.Infrastructure.Messengers.Discord
var builder = new ComponentBuilder(); var builder = new ComponentBuilder();
var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService)); var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
var moneyManagements = moneyManagementService.GetMoneyMangements(); // var moneyManagements = moneyManagementService.GetMoneyMangements();
// TODO Update this to get the money management from the account
var moneyManagements = new List<MoneyManagement>
{
new MoneyManagement
{
Name = "MediumRisk",
BalanceAtRisk = 0.05m,
Leverage = 1,
StopLoss = 0.021m,
TakeProfit = 0.042m,
Timeframe = Timeframe.FifteenMinutes
}
};
foreach (var mm in moneyManagements) foreach (var mm in moneyManagements)
{ {
var data = new CopyTradeData var data = new CopyTradeData

View File

@@ -1,13 +0,0 @@
namespace Managing.Infrastructure.MongoDb.Attributes
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class BsonCollectionAttribute : Attribute
{
public string CollectionName { get; }
public BsonCollectionAttribute(string collectionName)
{
CollectionName = collectionName;
}
}
}

View File

@@ -1,14 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.MongoDb.Collections;
[BsonCollection("Accounts")]
public class AccountDto : Document
{
public string Name { get; set; }
public Exchanges Exchanges { get; set; }
public string Key { get; set; }
public string Secret { get; set; }
}

View File

@@ -1,24 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.MongoDb.Collections
{
[BsonCollection("Backtests")]
public class BacktestDto : Document
{
public decimal FinalPnl { get; set; }
public int WinRate { get; set; }
public decimal GrowthPercentage { get; set; }
public decimal HodlPercentage { get; set; }
public string Ticker { get; set; }
public string Scenario { get; set; }
public List<PositionDto> Positions { get; set; }
public List<SignalDto> Signals { get; set; }
public Timeframe Timeframe { get; set; }
public RiskLevel RiskLevel { get; set; }
public string AccountName { get; set; }
public List<CandleDto> Candles { get; set; }
public BotType BotType { get; set; }
}
}

View File

@@ -1,28 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using MongoDB.Bson.Serialization.Attributes;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.MongoDb.Collections
{
[BsonCollection("Candles")]
public class CandleDto : Document
{
public Exchanges 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; }
}
}

View File

@@ -1,18 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.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 QuantityTakeProfit { get; set; }
public decimal Leverage { get; set; }
}
}

View File

@@ -1,24 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using MongoDB.Bson.Serialization.Attributes;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.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 TradeStatus Status { get; set; }
public string SignalIdentifier { get; set; }
public string AccountName { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
namespace Managing.Infrastructure.MongoDb.Collections
{
[BsonCollection("Scenarios")]
public class ScenarioDto : Document
{
public string Name { get; set; }
public List<StrategyDto> Strategies { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.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 string Ticker { get; set; }
public SignalStatus Status { get; set; }
public Timeframe Timeframe { get; set; }
public StrategyType Type { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.MongoDb.Collections
{
[BsonCollection("Strategies")]
public class StrategyDto : Document
{
public StrategyType Type { get; set; }
public string Name { 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; }
}
}

View File

@@ -1,15 +0,0 @@
using Managing.Infrastructure.MongoDb.Attributes;
using Managing.Infrastructure.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.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 Exchanges Exchange { get; set; }
}

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