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:
@@ -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.
|
|
||||||
|
|
||||||
@@ -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
18
.gitignore
vendored
@@ -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/
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -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
4
definition-pinky
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"dockerfilePath": "./src/Managing.Pinky/Dockerfile-pinky"
|
||||||
|
}
|
||||||
4
definition-proxy-api
Normal file
4
definition-proxy-api
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"dockerfilePath": "./src/Managing.Web3Proxy/Dockerfile-web3proxy"
|
||||||
|
}
|
||||||
191
scripts/README.md
Normal file
191
scripts/README.md
Normal 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
|
||||||
110
scripts/add-js-extensions.js
Normal file
110
scripts/add-js-extensions.js
Normal 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
130
scripts/add-js-extensions.mjs
Executable 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
487
scripts/package-lock.json
generated
Normal 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
9
scripts/package.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "managing-scripts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^10.3.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
74
scripts/remove-json-assertions.mjs
Normal file
74
scripts/remove-json-assertions.mjs
Normal 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
124
scripts/update-json-imports.mjs
Executable 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
BIN
src/.DS_Store
vendored
Binary file not shown.
@@ -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/"]
|
||||||
|
|||||||
@@ -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/"]
|
||||||
|
|||||||
@@ -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/"]
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,13 +111,40 @@ public class BotController : ControllerBase
|
|||||||
[Route("Start")]
|
[Route("Start")]
|
||||||
public async Task<ActionResult<string>> Start(StartBotRequest request)
|
public async Task<ActionResult<string>> Start(StartBotRequest request)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if user owns the account
|
||||||
|
if (!await UserOwnsBotAccount(request.BotName, request.AccountName))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to start this bot");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger error if money management is not provided
|
||||||
|
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,
|
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
|
||||||
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName,
|
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
|
||||||
request.IsForWatchOnly));
|
request.IsForWatchOnly));
|
||||||
|
|
||||||
await NotifyBotSubscriberAsync();
|
await NotifyBotSubscriberAsync();
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error starting bot");
|
||||||
|
return StatusCode(500, $"Error starting bot: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops a bot specified by type and name.
|
/// Stops a bot specified by type and name.
|
||||||
@@ -72,6 +156,14 @@ 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)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if user owns the account
|
||||||
|
if (!await UserOwnsBotAccount(botName))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to stop this bot");
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
||||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||||
|
|
||||||
@@ -79,6 +171,12 @@ public class BotController : ControllerBase
|
|||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error stopping bot");
|
||||||
|
return StatusCode(500, $"Error stopping bot: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a bot specified by name.
|
/// Deletes a bot specified by name.
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 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);
|
var result = await _botService.DeleteBot(botName);
|
||||||
await NotifyBotSubscriberAsync();
|
await NotifyBotSubscriberAsync();
|
||||||
return Ok(result);
|
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()
|
||||||
|
{
|
||||||
|
// This method should be restricted to only stop bots owned by the current user
|
||||||
|
var user = await GetUser();
|
||||||
|
if (user == null)
|
||||||
|
return "No authenticated user found";
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var bots = await GetBotList();
|
var bots = await GetBotList();
|
||||||
var result = "";
|
// Filter to only include bots owned by the current user
|
||||||
|
var userBots = new List<ApiTradingBot>();
|
||||||
|
|
||||||
foreach (var bot in bots)
|
foreach (var bot in bots)
|
||||||
{
|
{
|
||||||
result += $"{bot.Name} : ";
|
var account = await _accountService.GetAccount(bot.AccountName, true, false);
|
||||||
result += await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
// Compare the user names
|
||||||
result += $" |";
|
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();
|
await NotifyBotSubscriberAsync();
|
||||||
|
return "All your bots have been stopped successfully!";
|
||||||
return result;
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error stopping all bots");
|
||||||
|
return $"Error stopping bots: {ex.Message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -126,6 +261,14 @@ 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)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if user owns the account
|
||||||
|
if (!await UserOwnsBotAccount(botName))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to restart this bot");
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
|
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
|
||||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||||
|
|
||||||
@@ -133,27 +276,65 @@ public class BotController : ControllerBase
|
|||||||
|
|
||||||
return Ok(result);
|
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 user = await GetUser();
|
||||||
|
if (user == null)
|
||||||
|
return "No authenticated user found";
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var bots = await GetBotList();
|
var bots = await GetBotList();
|
||||||
var result = "";
|
// Filter to only include bots owned by the current user
|
||||||
|
var userBots = new List<ApiTradingBot>();
|
||||||
|
var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
|
||||||
|
|
||||||
foreach (var bot in bots)
|
foreach (var bot in bots)
|
||||||
{
|
{
|
||||||
result += $"{bot.Name} : ";
|
var account = await accountService.GetAccount(bot.AccountName, true, false);
|
||||||
result += await _mediator.Send(new RestartBotCommand(bot.BotType, bot.Name));
|
// Compare the user names
|
||||||
result += $" |";
|
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();
|
await NotifyBotSubscriberAsync();
|
||||||
|
return "All your bots have been restarted successfully!";
|
||||||
return result;
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Failed to restart all bots");
|
||||||
|
return $"Error restarting bots: {e.Message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -165,6 +346,14 @@ public class BotController : ControllerBase
|
|||||||
[Route("ToggleIsForWatching")]
|
[Route("ToggleIsForWatching")]
|
||||||
public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
|
public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if user owns the account
|
||||||
|
if (!await UserOwnsBotAccount(botName))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to modify this bot");
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
|
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
|
||||||
_logger.LogInformation($"{botName} bot is now {result}");
|
_logger.LogInformation($"{botName} bot is now {result}");
|
||||||
|
|
||||||
@@ -172,13 +361,19 @@ public class BotController : ControllerBase
|
|||||||
|
|
||||||
return Ok(result);
|
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>
|
||||||
/// Retrieves a list of active bots.
|
/// Retrieves a list of active bots.
|
||||||
/// </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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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/"]
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
"Organization": "",
|
"Organization": "",
|
||||||
"Token": ""
|
"Token": ""
|
||||||
},
|
},
|
||||||
|
"Web3Proxy": {
|
||||||
|
"BaseUrl": "http://localhost:3000"
|
||||||
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>()))
|
||||||
|
|||||||
@@ -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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -81,5 +85,13 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,4 +4,5 @@ public interface ISettingsService
|
|||||||
{
|
{
|
||||||
bool SetupSettings();
|
bool SetupSettings();
|
||||||
Task<bool> ResetSettings();
|
Task<bool> ResetSettings();
|
||||||
|
Task<bool> CreateDefaultConfiguration(Domain.Users.User user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 test = await ExchangeService.CancelOrder(Account, Ticker);
|
||||||
|
|
||||||
|
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);
|
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
|
||||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
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);
|
_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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,10 +13,13 @@ 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)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Get Trade
|
// Get Trade
|
||||||
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
||||||
@@ -46,4 +52,12 @@ public class ClosePositionCommandHandler(
|
|||||||
|
|
||||||
return request.Position;
|
return request.Position;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the error - regardless of the error type
|
||||||
|
logger?.LogError(ex, "Error closing position: {Message}", ex.Message);
|
||||||
|
|
||||||
|
throw new Exception(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,14 +47,17 @@ 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
|
var openPrice = request.IsForPaperTrading || request.Price.HasValue
|
||||||
? request.Price.Value
|
? request.Price.Value
|
||||||
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
|
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
|
||||||
|
|
||||||
var trade = exchangeService.OpenTrade(
|
// Determine SL/TP Prices
|
||||||
|
var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement);
|
||||||
|
var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement);
|
||||||
|
|
||||||
|
var trade = await exchangeService.OpenTrade(
|
||||||
account,
|
account,
|
||||||
request.Ticker,
|
request.Ticker,
|
||||||
request.Direction,
|
request.Direction,
|
||||||
@@ -63,23 +66,21 @@ namespace Managing.Application.Trading
|
|||||||
request.MoneyManagement.Leverage,
|
request.MoneyManagement.Leverage,
|
||||||
TradeType.Limit,
|
TradeType.Limit,
|
||||||
isForPaperTrading: request.IsForPaperTrading,
|
isForPaperTrading: request.IsForPaperTrading,
|
||||||
currentDate: request.Date).Result;
|
currentDate: request.Date,
|
||||||
|
stopLossPrice: stopLossPrice, // Pass determined SL price
|
||||||
|
takeProfitPrice: takeProfitPrice); // Pass determined TP price
|
||||||
|
|
||||||
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
||||||
return trade;
|
position.Open = trade;
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (IsOpenTradeHandled(position.Open.Status, account.Exchange) && !request.IgnoreSLTP.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
var closeDirection = request.Direction == TradeDirection.Long
|
var closeDirection = request.Direction == TradeDirection.Long
|
||||||
? TradeDirection.Short
|
? TradeDirection.Short
|
||||||
: TradeDirection.Long;
|
: TradeDirection.Long;
|
||||||
|
|
||||||
// Stop loss
|
// Stop loss - Use the determined price
|
||||||
position.StopLoss = exchangeService.BuildEmptyTrade(
|
position.StopLoss = exchangeService.BuildEmptyTrade(
|
||||||
request.Ticker,
|
request.Ticker,
|
||||||
RiskHelpers.GetStopLossPrice(request.Direction, position.Open.Price, request.MoneyManagement),
|
stopLossPrice, // Use determined SL price
|
||||||
position.Open.Quantity,
|
position.Open.Quantity,
|
||||||
closeDirection,
|
closeDirection,
|
||||||
request.MoneyManagement.Leverage,
|
request.MoneyManagement.Leverage,
|
||||||
@@ -90,10 +91,10 @@ namespace Managing.Application.Trading
|
|||||||
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
|
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
|
||||||
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
|
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
|
||||||
|
|
||||||
// Take profit
|
// Take profit - Use the determined price
|
||||||
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
||||||
request.Ticker,
|
request.Ticker,
|
||||||
RiskHelpers.GetTakeProfitPrice(request.Direction, position.Open.Price, request.MoneyManagement),
|
takeProfitPrice, // Use determined TP price
|
||||||
quantity,
|
quantity,
|
||||||
closeDirection,
|
closeDirection,
|
||||||
request.MoneyManagement.Leverage,
|
request.MoneyManagement.Leverage,
|
||||||
@@ -101,10 +102,6 @@ namespace Managing.Application.Trading
|
|||||||
request.Date,
|
request.Date,
|
||||||
TradeStatus.PendingOpen);
|
TradeStatus.PendingOpen);
|
||||||
|
|
||||||
position.TakeProfit1.Fee = TradingHelpers.GetFeeAmount(fee,
|
|
||||||
position.TakeProfit1.Price * position.TakeProfit1.Quantity, account.Exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
|
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
|
||||||
? position.Status
|
? position.Status
|
||||||
: PositionStatus.Rejected;
|
: PositionStatus.Rejected;
|
||||||
|
|||||||
@@ -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)
|
|
||||||
{
|
|
||||||
// User and account found
|
|
||||||
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
|
||||||
|
|
||||||
if (!user.Name.Equals(name))
|
|
||||||
throw new Exception("Name not corresponding");
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// No account and no
|
|
||||||
account = new Account
|
|
||||||
{
|
|
||||||
Name = $"Auth-{new Random().Next(1, 99)}",
|
|
||||||
Key = recoveredAddress,
|
|
||||||
Secret = "",
|
|
||||||
Exchange = Common.Enums.TradingExchanges.Evm,
|
|
||||||
Type = Common.Enums.AccountType.Auth,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
_ = await _accountService.CreateAccount(user, account);
|
if (!user.Name.Equals(name))
|
||||||
|
throw new Exception("Name not corresponding");
|
||||||
|
|
||||||
|
// User and account found
|
||||||
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
||||||
|
|
||||||
|
// Check if recoverred address owns the account
|
||||||
|
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 user found, we create one
|
// First login
|
||||||
// Create user if not existing
|
user = new User
|
||||||
user = new User()
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name
|
||||||
Accounts = new List<Account> { account },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_ = await _accountService.CreateAccount(user, account);
|
|
||||||
await _userRepository.InsertUserAsync(user);
|
await _userRepository.InsertUserAsync(user);
|
||||||
}
|
|
||||||
|
// Create embedded account to authenticate 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;
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.ToBinanceTicker(ticker),
|
||||||
BinanceHelpers.Map(timeframe), startDate);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class FtxProcessor : BaseProcessor
|
|||||||
{
|
{
|
||||||
balance += item.UsdValue;
|
balance += item.UsdValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ 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);
|
||||||
|
|
||||||
@@ -80,7 +82,8 @@ public class FtxProcessor : BaseProcessor
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -101,6 +104,7 @@ public class FtxProcessor : BaseProcessor
|
|||||||
{
|
{
|
||||||
return ftxKlines.ToList().LastOrDefault().ClosePrice;
|
return ftxKlines.ToList().LastOrDefault().ClosePrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,20 +130,27 @@ 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),
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user