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
|
||||
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.
|
||||
|
||||
@@ -15,6 +15,15 @@ You are a senior .NET backend developer and experimental quant with deep experti
|
||||
- Validate models with historical backtesting frameworks
|
||||
- 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
|
||||
- Write concise, idiomatic C# code with accurate examples.
|
||||
- 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
|
||||
- Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment).
|
||||
- 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
|
||||
- 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.
|
||||
- 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
|
||||
- Write unit tests using xUnit, NUnit, or MSTest.
|
||||
- Use Moq or NSubstitute for mocking dependencies.
|
||||
- Write unit tests using xUnit.
|
||||
- Use Mock or NSubstitute for mocking dependencies.
|
||||
- Implement integration tests for API endpoints.
|
||||
|
||||
## Security
|
||||
- Use Authentication and Authorization middleware.
|
||||
- Implement JWT authentication for stateless API authentication.
|
||||
- Use HTTPS and enforce SSL.
|
||||
- Implement proper CORS policies.
|
||||
- Give me advice when you see that some data should be carefully handled
|
||||
|
||||
## API Documentation
|
||||
- Use Swagger/OpenAPI for API documentation (as per installed Swashbuckle.AspNetCore package).
|
||||
- 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.
|
||||
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.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
|
||||
|
||||
## 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
|
||||
|
||||
- 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/).
|
||||
|
||||
# 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.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||
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.Core/Managing.Core.csproj", "Managing.Core/"]
|
||||
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.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||
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.Core/Managing.Core.csproj", "Managing.Core/"]
|
||||
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.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||
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.Core/Managing.Core.csproj", "Managing.Core/"]
|
||||
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Managing.Api.Controllers;
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class BacktestController : ControllerBase
|
||||
public class BacktestController : BaseController
|
||||
{
|
||||
private readonly IHubContext<BotHub> _hubContext;
|
||||
private readonly IBacktester _backtester;
|
||||
@@ -40,7 +40,8 @@ public class BacktestController : ControllerBase
|
||||
IBacktester backtester,
|
||||
IScenarioService scenarioService,
|
||||
IAccountService accountService,
|
||||
IMoneyManagementService moneyManagementService)
|
||||
IMoneyManagementService moneyManagementService,
|
||||
IUserService userService) : base(userService)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
_backtester = backtester;
|
||||
@@ -50,24 +51,46 @@ public class BacktestController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all backtests.
|
||||
/// Retrieves all backtests for the authenticated user.
|
||||
/// </summary>
|
||||
/// <returns>A list of backtests.</returns>
|
||||
[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>
|
||||
/// 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>
|
||||
/// <param name="id">The ID of the backtest to delete.</param>
|
||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||
[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>
|
||||
@@ -90,7 +113,8 @@ public class BacktestController : ControllerBase
|
||||
/// <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="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="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>
|
||||
@@ -104,9 +128,10 @@ public class BacktestController : ControllerBase
|
||||
string scenarioName,
|
||||
Timeframe timeframe,
|
||||
bool watchOnly,
|
||||
int days,
|
||||
decimal balance,
|
||||
string moneyManagementName,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
MoneyManagement? moneyManagement = null,
|
||||
bool save = false)
|
||||
{
|
||||
@@ -127,18 +152,14 @@ public class BacktestController : ControllerBase
|
||||
nameof(moneyManagementName));
|
||||
}
|
||||
|
||||
if (days > 0)
|
||||
{
|
||||
days = days * -1;
|
||||
}
|
||||
|
||||
Backtest backtestResult = null;
|
||||
var scenario = _scenarioService.GetScenario(scenarioName);
|
||||
var account = await _accountService.GetAccount(accountName, true, false);
|
||||
var user = await GetUser();
|
||||
|
||||
if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null)
|
||||
{
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName);
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -148,21 +169,37 @@ public class BacktestController : ControllerBase
|
||||
if (scenario == null)
|
||||
return BadRequest("No scenario found");
|
||||
|
||||
// var localCandles = FileHelpers
|
||||
// .ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json")
|
||||
// .TakeLast(500).ToList();
|
||||
|
||||
switch (botType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
break;
|
||||
case BotType.ScalpingBot:
|
||||
backtestResult = _backtester.RunScalpingBotBacktest(account, moneyManagement, ticker, scenario,
|
||||
timeframe, Convert.ToDouble(days), balance, watchOnly, save);
|
||||
backtestResult = await _backtester.RunScalpingBotBacktest(
|
||||
account,
|
||||
moneyManagement,
|
||||
ticker,
|
||||
scenario,
|
||||
timeframe,
|
||||
balance,
|
||||
startDate,
|
||||
endDate,
|
||||
user,
|
||||
watchOnly,
|
||||
save);
|
||||
break;
|
||||
case BotType.FlippingBot:
|
||||
backtestResult = _backtester.RunFlippingBotBacktest(account, moneyManagement, ticker, scenario,
|
||||
timeframe, Convert.ToDouble(days), balance, watchOnly, save);
|
||||
backtestResult = await _backtester.RunFlippingBotBacktest(
|
||||
account,
|
||||
moneyManagement,
|
||||
ticker,
|
||||
scenario,
|
||||
timeframe,
|
||||
balance,
|
||||
startDate,
|
||||
endDate,
|
||||
user,
|
||||
watchOnly,
|
||||
save);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,22 @@ using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Users;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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 ApplicationTradingBot = Managing.Application.Bots.TradingBot;
|
||||
using ApiTradingBot = Managing.Api.Models.Responses.TradingBot;
|
||||
|
||||
namespace Managing.Api.Controllers;
|
||||
|
||||
@@ -20,13 +31,15 @@ namespace Managing.Api.Controllers;
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class BotController : ControllerBase
|
||||
public class BotController : BaseController
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<BotController> _logger;
|
||||
private readonly IHubContext<BotHub> _hubContext;
|
||||
private readonly IBacktester _backtester;
|
||||
private readonly IBotService _botService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
|
||||
/// <summary>
|
||||
/// 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="backtester">Backtester for running backtests on bots.</param>
|
||||
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;
|
||||
_mediator = mediator;
|
||||
_hubContext = hubContext;
|
||||
_backtester = backtester;
|
||||
_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>
|
||||
@@ -54,12 +111,39 @@ public class BotController : ControllerBase
|
||||
[Route("Start")]
|
||||
public async Task<ActionResult<string>> Start(StartBotRequest request)
|
||||
{
|
||||
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
|
||||
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName,
|
||||
request.IsForWatchOnly));
|
||||
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");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
// 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,
|
||||
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
|
||||
request.IsForWatchOnly));
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting bot");
|
||||
return StatusCode(500, $"Error starting bot: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,12 +156,26 @@ public class BotController : ControllerBase
|
||||
[Route("Stop")]
|
||||
public async Task<ActionResult<string>> Stop(BotType botType, string botName)
|
||||
{
|
||||
var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
{
|
||||
return Forbid("You don't have permission to stop this bot");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
|
||||
return Ok(result);
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error stopping bot");
|
||||
return StatusCode(500, $"Error stopping bot: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,31 +187,68 @@ public class BotController : ControllerBase
|
||||
[Route("Delete")]
|
||||
public async Task<ActionResult<bool>> Delete(string botName)
|
||||
{
|
||||
var result = await _botService.DeleteBot(botName);
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
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);
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting bot");
|
||||
return StatusCode(500, $"Error deleting bot: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops all active bots.
|
||||
/// </summary>
|
||||
/// <returns>A string summarizing the results of the stop operations for all bots.</returns>
|
||||
[HttpGet]
|
||||
[Route("StopAll")]
|
||||
[HttpPost("stop-all")]
|
||||
public async Task<string> StopAll()
|
||||
{
|
||||
var bots = await GetBotList();
|
||||
var result = "";
|
||||
foreach (var bot in bots)
|
||||
// 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
|
||||
{
|
||||
result += $"{bot.Name} : ";
|
||||
result += await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
||||
result += $" |";
|
||||
var bots = await GetBotList();
|
||||
// Filter to only include bots owned by the current user
|
||||
var userBots = new List<ApiTradingBot>();
|
||||
|
||||
foreach (var bot in bots)
|
||||
{
|
||||
var account = await _accountService.GetAccount(bot.AccountName, true, false);
|
||||
// Compare the user names
|
||||
if (account != null && account.User != null && account.User.Name == user.Name)
|
||||
{
|
||||
userBots.Add(bot);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var bot in userBots)
|
||||
{
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {bot.Name} paused by {user.Name}.", "Info");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
return "All your bots have been stopped successfully!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error stopping all bots");
|
||||
return $"Error stopping bots: {ex.Message}";
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,34 +261,80 @@ public class BotController : ControllerBase
|
||||
[Route("Restart")]
|
||||
public async Task<ActionResult<string>> Restart(BotType botType, string botName)
|
||||
{
|
||||
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
{
|
||||
return Forbid("You don't have permission to restart this bot");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
|
||||
return Ok(result);
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error restarting bot");
|
||||
return StatusCode(500, $"Error restarting bot: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts all active bots.
|
||||
/// </summary>
|
||||
/// <returns>A string summarizing the results of the restart operations for all bots.</returns>
|
||||
[HttpGet]
|
||||
[Route("RestartAll")]
|
||||
[HttpPost("restart-all")]
|
||||
public async Task<string> RestartAll()
|
||||
{
|
||||
var bots = await GetBotList();
|
||||
var result = "";
|
||||
foreach (var bot in bots)
|
||||
var user = await GetUser();
|
||||
if (user == null)
|
||||
return "No authenticated user found";
|
||||
|
||||
try
|
||||
{
|
||||
result += $"{bot.Name} : ";
|
||||
result += await _mediator.Send(new RestartBotCommand(bot.BotType, bot.Name));
|
||||
result += $" |";
|
||||
var bots = await GetBotList();
|
||||
// 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)
|
||||
{
|
||||
var account = await accountService.GetAccount(bot.AccountName, true, false);
|
||||
// Compare the user names
|
||||
if (account != null && account.User != null && account.User.Name == user.Name)
|
||||
{
|
||||
userBots.Add(bot);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var bot in userBots)
|
||||
{
|
||||
// We can't directly restart a bot with just BotType and Name
|
||||
// Instead, stop the bot and then retrieve the backup to start it again
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
||||
|
||||
// Get the saved bot backup
|
||||
var backup = _botService.GetBotBackup(bot.Name);
|
||||
if (backup != null)
|
||||
{
|
||||
_botService.StartBotFromBackup(backup);
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {bot.Name} restarted by {user.Name}.", "Info");
|
||||
}
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
return "All your bots have been restarted successfully!";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to restart all bots");
|
||||
return $"Error restarting bots: {e.Message}";
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -165,12 +346,26 @@ public class BotController : ControllerBase
|
||||
[Route("ToggleIsForWatching")]
|
||||
public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
|
||||
{
|
||||
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
|
||||
_logger.LogInformation($"{botName} bot is now {result}");
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
{
|
||||
return Forbid("You don't have permission to modify this bot");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
|
||||
_logger.LogInformation($"{botName} bot is now {result}");
|
||||
|
||||
return Ok(result);
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error toggling bot watching status");
|
||||
return StatusCode(500, $"Error toggling bot watching status: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,7 +373,7 @@ public class BotController : ControllerBase
|
||||
/// </summary>
|
||||
/// <returns>A list of active trading bots.</returns>
|
||||
[HttpGet]
|
||||
public async Task<List<TradingBot>> GetActiveBots()
|
||||
public async Task<List<ApiTradingBot>> GetActiveBots()
|
||||
{
|
||||
return await GetBotList();
|
||||
}
|
||||
@@ -187,28 +382,28 @@ public class BotController : ControllerBase
|
||||
/// Retrieves a list of active bots by sending a command to the mediator.
|
||||
/// </summary>
|
||||
/// <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 list = new List<TradingBot>();
|
||||
var list = new List<ApiTradingBot>();
|
||||
|
||||
foreach (var item in result)
|
||||
{
|
||||
list.Add(new TradingBot
|
||||
list.Add(new ApiTradingBot
|
||||
{
|
||||
Status = item.GetStatus(),
|
||||
Name = item.GetName(),
|
||||
Candles = item.OptimizedCandles.ToList(),
|
||||
Positions = item.Positions,
|
||||
Name = item.Name,
|
||||
Signals = item.Signals.ToList(),
|
||||
Positions = item.Positions,
|
||||
Candles = item.Candles.ToList(),
|
||||
WinRate = item.GetWinRate(),
|
||||
ProfitAndLoss = item.GetProfitAndLoss(),
|
||||
Timeframe = item.Timeframe,
|
||||
Ticker = item.Ticker,
|
||||
AccountName = item.AccountName,
|
||||
Scenario = item.ScenarioName,
|
||||
IsForWatchingOnly = item.IsForWatchingOnly,
|
||||
BotType = item.BotType,
|
||||
AccountName = item.AccountName,
|
||||
MoneyManagement = item.MoneyManagement
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DataController : ControllerBase
|
||||
var cacheKey = string.Concat(timeframe.ToString());
|
||||
var tickers = _cacheService.GetValue<List<Ticker>>(cacheKey);
|
||||
|
||||
if (tickers == null)
|
||||
if (tickers == null || tickers.Count == 0)
|
||||
{
|
||||
tickers = await _exchangeService.GetTickers(timeframe);
|
||||
_cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -14,7 +15,7 @@ namespace Managing.Api.Controllers;
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class MoneyManagementController : ControllerBase
|
||||
public class MoneyManagementController : BaseController
|
||||
{
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
|
||||
@@ -22,52 +23,60 @@ public class MoneyManagementController : ControllerBase
|
||||
/// Initializes a new instance of the <see cref="MoneyManagementController"/> class.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="moneyManagement">The money management strategy to create or update.</param>
|
||||
/// <returns>The created or updated money management strategy.</returns>
|
||||
[HttpPost]
|
||||
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>
|
||||
/// Retrieves all money management strategies.
|
||||
/// Retrieves all money management strategies for the authenticated user.
|
||||
/// </summary>
|
||||
/// <returns>A list of money management strategies.</returns>
|
||||
[HttpGet]
|
||||
[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>
|
||||
/// Retrieves a specific money management strategy by name.
|
||||
/// Retrieves a specific money management strategy by name for the authenticated user.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the money management strategy to retrieve.</param>
|
||||
/// <returns>The requested money management strategy if found.</returns>
|
||||
[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>
|
||||
/// Deletes a specific money management strategy by name.
|
||||
/// Deletes a specific money management strategy by name for the authenticated user.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the money management strategy to delete.</param>
|
||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||
[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.Services;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -16,7 +17,7 @@ namespace Managing.Api.Controllers;
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class ScenarioController : ControllerBase
|
||||
public class ScenarioController : BaseController
|
||||
{
|
||||
private readonly IScenarioService _scenarioService;
|
||||
|
||||
@@ -24,67 +25,75 @@ public class ScenarioController : ControllerBase
|
||||
/// Initializes a new instance of the <see cref="ScenarioController"/> class.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all scenarios.
|
||||
/// Retrieves all scenarios for the authenticated user.
|
||||
/// </summary>
|
||||
/// <returns>A list of scenarios.</returns>
|
||||
[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>
|
||||
/// 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>
|
||||
/// <param name="name">The name of the scenario.</param>
|
||||
/// <param name="strategies">A list of strategy names to include in the scenario.</param>
|
||||
/// <returns>The created scenario.</returns>
|
||||
[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>
|
||||
/// Deletes a scenario by name.
|
||||
/// Deletes a scenario by name for the authenticated user.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the scenario to delete.</param>
|
||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||
[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
|
||||
[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>
|
||||
/// Retrieves all strategies.
|
||||
/// Retrieves all strategies for the authenticated user.
|
||||
/// </summary>
|
||||
/// <returns>A list of strategies.</returns>
|
||||
[HttpGet]
|
||||
[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>
|
||||
/// Creates a new strategy with specified parameters.
|
||||
/// Creates a new strategy with specified parameters for the authenticated user.
|
||||
/// </summary>
|
||||
/// <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="period">The period 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>
|
||||
[HttpPost]
|
||||
[Route("strategy")]
|
||||
public ActionResult<Strategy> CreateStrategy(
|
||||
public async Task<ActionResult<Strategy>> CreateStrategy(
|
||||
StrategyType strategyType,
|
||||
string name,
|
||||
int? period = null,
|
||||
@@ -109,7 +118,9 @@ public class ScenarioController : ControllerBase
|
||||
int? smoothPeriods = null,
|
||||
int? cyclePeriods = null)
|
||||
{
|
||||
return Ok(_scenarioService.CreateStrategy(
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.CreateStrategyForUser(
|
||||
user,
|
||||
strategyType,
|
||||
name,
|
||||
period,
|
||||
@@ -123,21 +134,22 @@ public class ScenarioController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a strategy by name.
|
||||
/// Deletes a strategy by name for the authenticated user.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the strategy to delete.</param>
|
||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||
[HttpDelete]
|
||||
[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
|
||||
[HttpPut]
|
||||
[Route("strategy")]
|
||||
public ActionResult UpdateStrategy(
|
||||
public async Task<ActionResult> UpdateStrategy(
|
||||
StrategyType strategyType,
|
||||
string name,
|
||||
int? period = null,
|
||||
@@ -149,7 +161,9 @@ public class ScenarioController : ControllerBase
|
||||
int? smoothPeriods = null,
|
||||
int? cyclePeriods = null)
|
||||
{
|
||||
return Ok(_scenarioService.UpdateStrategy(
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.UpdateStrategyByUser(
|
||||
user,
|
||||
strategyType,
|
||||
name,
|
||||
period,
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.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;
|
||||
|
||||
@@ -8,10 +18,12 @@ namespace Managing.Api.Controllers;
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class SettingsController : ControllerBase
|
||||
public class SettingsController : BaseController
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
public SettingsController(ISettingsService settingsService)
|
||||
|
||||
public SettingsController(ISettingsService settingsService, IUserService userService)
|
||||
: base(userService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
@@ -27,4 +39,26 @@ public class SettingsController : ControllerBase
|
||||
{
|
||||
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]
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
public class TradingController : ControllerBase
|
||||
public class TradingController : BaseController
|
||||
{
|
||||
private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler;
|
||||
private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler;
|
||||
@@ -39,7 +39,8 @@ public class TradingController : ControllerBase
|
||||
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
|
||||
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
|
||||
ITradingService tradingService,
|
||||
IMediator mediator, IMoneyManagementService moneyManagementService)
|
||||
IMediator mediator, IMoneyManagementService moneyManagementService,
|
||||
IUserService userService) : base(userService)
|
||||
{
|
||||
_logger = logger;
|
||||
_openTradeCommandHandler = openTradeCommandHandler;
|
||||
@@ -137,7 +138,8 @@ public class TradingController : ControllerBase
|
||||
|
||||
if (moneyManagement != null)
|
||||
{
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName);
|
||||
var user = await GetUser();
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
||||
}
|
||||
|
||||
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.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||
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.Core/Managing.Core.csproj", "Managing.Core/"]
|
||||
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
||||
|
||||
@@ -9,37 +9,19 @@ namespace Managing.Api.Models.Responses
|
||||
{
|
||||
public class TradingBot
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; internal set; }
|
||||
[Required]
|
||||
public string Status { get; internal set; }
|
||||
[Required]
|
||||
public List<Signal> Signals { get; internal set; }
|
||||
[Required]
|
||||
public List<Position> Positions { get; internal set; }
|
||||
[Required]
|
||||
public List<Candle> Candles { get; internal set; }
|
||||
[Required]
|
||||
public RiskLevel RiskLevel { get; internal set; }
|
||||
[Required]
|
||||
public int WinRate { 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; }
|
||||
[Required] public string Name { get; internal set; }
|
||||
[Required] public string Status { get; internal set; }
|
||||
[Required] public List<Signal> Signals { get; internal set; }
|
||||
[Required] public List<Position> Positions { get; internal set; }
|
||||
[Required] public List<Candle> Candles { get; internal set; }
|
||||
[Required] public int WinRate { 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 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",
|
||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
||||
},
|
||||
"Web3Proxy": {
|
||||
"BaseUrl": "http://srv-captain--web3-proxy:4111"
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"Organization": "",
|
||||
"Token": ""
|
||||
},
|
||||
"Web3Proxy": {
|
||||
"BaseUrl": "http://localhost:3000"
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Application.Abstractions.Repositories;
|
||||
|
||||
public interface IBacktestRepository
|
||||
{
|
||||
void InsertBacktest(Backtest result);
|
||||
IEnumerable<Backtest> GetBacktests();
|
||||
void DeleteBacktestById(string id);
|
||||
void DeleteAllBacktests();
|
||||
void InsertBacktestForUser(User user, Backtest result);
|
||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||
Backtest GetBacktestByIdForUser(User user, string id);
|
||||
void DeleteBacktestByIdForUser(User user, string id);
|
||||
void DeleteAllBacktestsForUser(User user);
|
||||
}
|
||||
@@ -10,6 +10,14 @@ public interface ICandleRepository
|
||||
Enums.Ticker ticker,
|
||||
Enums.Timeframe timeframe,
|
||||
DateTime start);
|
||||
|
||||
Task<IList<Candle>> GetCandles(
|
||||
Enums.TradingExchanges exchange,
|
||||
Enums.Ticker ticker,
|
||||
Enums.Timeframe timeframe,
|
||||
DateTime start,
|
||||
DateTime end);
|
||||
|
||||
Task<IList<Enums.Ticker>> GetTickersAsync(
|
||||
Enums.TradingExchanges exchange,
|
||||
Enums.Timeframe timeframe,
|
||||
|
||||
@@ -25,7 +25,7 @@ public interface IEvmManager
|
||||
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
|
||||
Task<List<Ticker>> GetAvailableTicker();
|
||||
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,
|
||||
string receiverAddress);
|
||||
@@ -34,7 +34,9 @@ public interface IEvmManager
|
||||
Task<bool> CancelOrders(Account account, Ticker ticker);
|
||||
|
||||
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);
|
||||
|
||||
@@ -44,11 +46,22 @@ public interface IEvmManager
|
||||
Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker);
|
||||
|
||||
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<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
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.Users;
|
||||
|
||||
namespace Managing.Application.Abstractions.Repositories;
|
||||
|
||||
@@ -10,4 +11,10 @@ public interface ISettingsRepository
|
||||
IEnumerable<MoneyManagement> GetMoneyManagements();
|
||||
void DeleteMoneyManagement(string name);
|
||||
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.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions.Repositories;
|
||||
@@ -9,6 +10,8 @@ public interface ITradingRepository
|
||||
{
|
||||
Scenario GetScenarioByName(string scenario);
|
||||
void InsertSignal(Signal signal);
|
||||
IEnumerable<Signal> GetSignalsByUser(User user);
|
||||
Signal GetSignalByIdentifier(string identifier, User user = null);
|
||||
void InsertPosition(Position position);
|
||||
void UpdatePosition(Position position);
|
||||
Strategy GetStrategyByName(string strategy);
|
||||
|
||||
@@ -6,4 +6,5 @@ public interface IUserRepository
|
||||
{
|
||||
Task<User> GetUserByNameAsync(string name);
|
||||
Task InsertUserAsync(User user);
|
||||
}
|
||||
Task UpdateUser(User user);
|
||||
}
|
||||
@@ -3,28 +3,54 @@ using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions.Services
|
||||
{
|
||||
public interface IBacktester
|
||||
{
|
||||
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker,
|
||||
Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false,
|
||||
bool save = false, List<Candle>? initialCandles = null);
|
||||
Task<Backtest> RunScalpingBotBacktest(
|
||||
Account account,
|
||||
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,
|
||||
Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false,
|
||||
bool save = false, List<Candle>? initialCandles = null);
|
||||
Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
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 DeleteBacktests();
|
||||
|
||||
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance);
|
||||
Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null);
|
||||
|
||||
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance);
|
||||
Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
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 isForPaperTrading = false,
|
||||
DateTime? currentDate = null,
|
||||
bool ioc = true);
|
||||
bool ioc = true,
|
||||
decimal? stopLossPrice = null,
|
||||
decimal? takeProfitPrice = null);
|
||||
|
||||
Task<decimal> GetBalance(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,
|
||||
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);
|
||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class BaseTests
|
||||
_moneyManagementService = new Mock<IMoneyManagementService>();
|
||||
MoneyManagement = new MoneyManagement()
|
||||
{
|
||||
BalanceAtRisk = 0.30m, // 30%
|
||||
BalanceAtRisk = 1m, // 30%
|
||||
Leverage = 2, // x2
|
||||
Timeframe = Timeframe.FifteenMinutes,
|
||||
StopLoss = 0.008m, // 0.8%
|
||||
@@ -32,7 +32,7 @@ public class BaseTests
|
||||
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));
|
||||
|
||||
_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.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using MongoDB.Driver.Linq;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
@@ -30,6 +31,7 @@ namespace Managing.Application.Tests
|
||||
{
|
||||
var backtestRepository = new Mock<IBacktestRepository>().Object;
|
||||
var discordService = new Mock<IMessengerService>().Object;
|
||||
var scenarioService = new Mock<IScenarioService>().Object;
|
||||
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
||||
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
||||
var botService = new Mock<IBotService>().Object;
|
||||
@@ -40,13 +42,14 @@ namespace Managing.Application.Tests
|
||||
_accountService.Object,
|
||||
_tradingService.Object,
|
||||
botService);
|
||||
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger);
|
||||
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
||||
scenarioService);
|
||||
_elapsedTimes = new List<double>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[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)
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,8 +60,9 @@ namespace Managing.Application.Tests
|
||||
FileHelpers.ReadJson<List<Candle>>($"{ticker.ToString()}-{timeframe.ToString()}-candles.json");
|
||||
|
||||
// Act
|
||||
var backtestResult = _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
||||
timeframe, Convert.ToDouble(days), 1000, initialCandles: localCandles.TakeLast(500).ToList());
|
||||
var backtestResult = await _backtester.RunFlippingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
||||
timeframe, 1000, new DateTime().AddDays(-3), DateTime.UtcNow,
|
||||
initialCandles: localCandles.TakeLast(500).ToList());
|
||||
|
||||
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
|
||||
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.FifteenMinutes, -4)]
|
||||
[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)
|
||||
{
|
||||
// Arrange
|
||||
@@ -87,8 +92,8 @@ namespace Managing.Application.Tests
|
||||
scenario.AddStrategy(strategy);
|
||||
|
||||
// Act
|
||||
var backtestResult = _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
||||
timeframe, Convert.ToDouble(days), 1000);
|
||||
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, MoneyManagement, ticker, scenario,
|
||||
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
|
||||
//WriteCsvReport(backtestResult.GetStringReport());
|
||||
|
||||
// Assert
|
||||
@@ -99,7 +104,7 @@ namespace Managing.Application.Tests
|
||||
|
||||
[Theory]
|
||||
[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)
|
||||
{
|
||||
// Arrange
|
||||
@@ -118,8 +123,8 @@ namespace Managing.Application.Tests
|
||||
};
|
||||
|
||||
// Act
|
||||
var backtestResult = _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario,
|
||||
timeframe, Convert.ToDouble(days), 1000);
|
||||
var backtestResult = await _backtester.RunScalpingBotBacktest(_account, moneyManagement, ticker, scenario,
|
||||
timeframe, 1000, DateTime.UtcNow.AddDays(-6), DateTime.UtcNow, null);
|
||||
WriteCsvReport(backtestResult.GetStringReport());
|
||||
|
||||
// Assert
|
||||
@@ -190,9 +195,9 @@ namespace Managing.Application.Tests
|
||||
{
|
||||
BotType.SimpleBot => throw new NotImplementedException(),
|
||||
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement,
|
||||
scenario, timeframe, candles, 1000),
|
||||
scenario, timeframe, candles, 1000, null).Result,
|
||||
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
|
||||
scenario, timeframe, candles, 1000),
|
||||
scenario, timeframe, candles, 1000, null).Result,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
timer.Stop();
|
||||
@@ -299,9 +304,9 @@ namespace Managing.Application.Tests
|
||||
{
|
||||
BotType.SimpleBot => throw new NotImplementedException(),
|
||||
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(_account, moneyManagement,
|
||||
scenario, timeframe, candles, 1000),
|
||||
scenario, timeframe, candles, 1000, null).Result,
|
||||
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(_account, moneyManagement,
|
||||
scenario, timeframe, candles, 1000),
|
||||
scenario, timeframe, candles, 1000, null).Result,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
|
||||
@@ -7,13 +7,16 @@ using Managing.Infrastructure.Databases.InfluxDb;
|
||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
||||
using Managing.Infrastructure.Evm;
|
||||
using Managing.Infrastructure.Evm.Abstractions;
|
||||
using Managing.Infrastructure.Evm.Models.Privy;
|
||||
using Managing.Infrastructure.Evm.Services;
|
||||
using Managing.Infrastructure.Evm.Subgraphs;
|
||||
using Managing.Infrastructure.Exchanges;
|
||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||
using Managing.Infrastructure.Exchanges.Exchanges;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Nethereum.Web3;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Tests
|
||||
@@ -34,7 +37,7 @@ namespace Managing.Application.Tests
|
||||
Chainlink,
|
||||
GbcFeed
|
||||
};
|
||||
var evmManager = new EvmManager(Subgraphs);
|
||||
var evmManager = new EvmManager(Subgraphs, CreateWebProxyService());
|
||||
var evmProcessor = new EvmProcessor(new Mock<ILogger<EvmProcessor>>().Object, evmManager);
|
||||
|
||||
var exchangeProcessors = new List<IExchangeProcessor>()
|
||||
@@ -44,7 +47,8 @@ namespace Managing.Application.Tests
|
||||
evmProcessor
|
||||
};
|
||||
|
||||
return new ExchangeService(loggerFactory.CreateLogger<ExchangeService>(), GetCandleRepository(), exchangeProcessors);
|
||||
return new ExchangeService(loggerFactory.CreateLogger<ExchangeService>(), GetCandleRepository(),
|
||||
exchangeProcessors);
|
||||
}
|
||||
|
||||
public static ILogger<TradingBot> CreateTradingBotLogger()
|
||||
@@ -72,14 +76,22 @@ namespace Managing.Application.Tests
|
||||
{
|
||||
var settings = new InfluxDbSettings()
|
||||
{
|
||||
Url = "http://localhost:8086/",
|
||||
Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==",
|
||||
Organization = "managing-org"
|
||||
Url = "http://localhost:8086/",
|
||||
Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==",
|
||||
Organization = "managing-org"
|
||||
};
|
||||
var influxdb = new InfluxDbRepository(settings);
|
||||
var candleRepository = new CandleRepository(influxdb, CreateCandleRepositoryLogger());
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
_ = Parallel.ForEach(tickers, options, ticker =>
|
||||
_ = Parallel.ForEach(tickers, options, async ticker =>
|
||||
{
|
||||
spotlight.TickerSignals.Add(new TickerSignal
|
||||
{
|
||||
Ticker = ticker,
|
||||
FiveMinutes = GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
|
||||
FifteenMinutes = GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
|
||||
OneHour = GetSignals(account, scenario, ticker, Timeframe.OneHour),
|
||||
FourHour = GetSignals(account, scenario, ticker, Timeframe.FourHour),
|
||||
OneDay = GetSignals(account, scenario, ticker, Timeframe.OneDay)
|
||||
FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
|
||||
FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
|
||||
OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour),
|
||||
FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour),
|
||||
OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -237,7 +237,7 @@ public class StatisticService : IStatisticService
|
||||
_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
|
||||
{
|
||||
@@ -250,14 +250,15 @@ public class StatisticService : IStatisticService
|
||||
TakeProfit = 0.02m
|
||||
};
|
||||
|
||||
var backtest = _backtester.RunScalpingBotBacktest(
|
||||
var backtest = await _backtester.RunScalpingBotBacktest(
|
||||
account,
|
||||
moneyManagement,
|
||||
ticker,
|
||||
scenario,
|
||||
timeframe,
|
||||
CandleExtensions.GetMinimalDays(timeframe),
|
||||
1000,
|
||||
DateTime.Now.AddDays(-7),
|
||||
DateTime.Now,
|
||||
isForWatchingOnly: true);
|
||||
|
||||
return backtest.Signals;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Workflows;
|
||||
@@ -14,6 +15,7 @@ public interface IBotService
|
||||
List<ITradingBot> GetActiveBots();
|
||||
IEnumerable<BotBackup> GetSavedBots();
|
||||
void StartBotFromBackup(BotBackup backupBot);
|
||||
BotBackup GetBotBackup(string name);
|
||||
|
||||
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
|
||||
string scenario, Enums.Timeframe interval, bool isForWatchingOnly);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Application.Abstractions
|
||||
{
|
||||
public interface IMoneyManagementService
|
||||
{
|
||||
Task<MoneyManagement> CreateOrUpdateMoneyManagement(MoneyManagement request);
|
||||
Task<MoneyManagement> GetMoneyMangement(string name);
|
||||
IEnumerable<MoneyManagement> GetMoneyMangements();
|
||||
bool DeleteMoneyManagement(string name);
|
||||
bool DeleteMoneyManagements();
|
||||
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
|
||||
Task<MoneyManagement> GetMoneyMangement(User user, string name);
|
||||
IEnumerable<MoneyManagement> GetMoneyMangements(User user);
|
||||
bool DeleteMoneyManagement(User user, string name);
|
||||
bool DeleteMoneyManagements(User user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions
|
||||
@@ -30,5 +31,31 @@ namespace Managing.Application.Abstractions
|
||||
|
||||
bool UpdateStrategy(StrategyType strategyType, string name, int? period, int? fastPeriods, int? slowPeriods,
|
||||
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
|
||||
|
||||
IEnumerable<Scenario> GetScenariosByUser(User user);
|
||||
Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
|
||||
IEnumerable<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();
|
||||
Task<bool> ResetSettings();
|
||||
Task<bool> CreateDefaultConfiguration(Domain.Users.User user);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
@@ -34,5 +35,6 @@ namespace Managing.Application.Abstractions
|
||||
void LoadStrategies(IEnumerable<IStrategy> strategies);
|
||||
void LoadScenario(string scenarioName);
|
||||
void UpdateStrategiesValues();
|
||||
Task LoadAccount();
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,10 @@ public class AccountService : IAccountService
|
||||
else if (request.Exchange == Enums.TradingExchanges.Evm
|
||||
&& 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();
|
||||
request.Key = privyClient.Address;
|
||||
request.Secret = privyClient.Id;
|
||||
|
||||
@@ -14,6 +14,7 @@ using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Application.Backtesting
|
||||
{
|
||||
@@ -42,21 +43,25 @@ namespace Managing.Application.Backtesting
|
||||
{
|
||||
var simplebot = _botFactory.CreateSimpleBot("scenario", workflow);
|
||||
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;
|
||||
}
|
||||
|
||||
public Backtest RunScalpingBotBacktest(Account account,
|
||||
public async Task<Backtest> RunScalpingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
Ticker ticker,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe,
|
||||
double days,
|
||||
decimal balance,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle> initialCandles = null)
|
||||
@@ -64,21 +69,36 @@ namespace Managing.Application.Backtesting
|
||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, isForWatchingOnly);
|
||||
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,
|
||||
moneyManagement);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
result.User = user;
|
||||
}
|
||||
|
||||
// Set start and end dates
|
||||
result.StartDate = startDate;
|
||||
result.EndDate = endDate;
|
||||
|
||||
if (save)
|
||||
{
|
||||
_backtestRepository.InsertBacktest(result);
|
||||
_backtestRepository.InsertBacktestForUser(user, 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,
|
||||
DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result;
|
||||
List<Candle> candles;
|
||||
|
||||
// Use specific date range
|
||||
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
|
||||
startDate, timeframe, endDate).Result;
|
||||
|
||||
if (candles == null || candles.Count == 0)
|
||||
throw new Exception($"No candles for {ticker} on {account.Exchange}");
|
||||
@@ -86,46 +106,85 @@ namespace Managing.Application.Backtesting
|
||||
return candles;
|
||||
}
|
||||
|
||||
public Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker,
|
||||
Scenario scenario, Timeframe timeframe,
|
||||
double days, decimal balance, bool isForWatchingOnly = false, bool save = false,
|
||||
public async Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
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)
|
||||
{
|
||||
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, false);
|
||||
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,
|
||||
moneyManagement);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
result.User = user;
|
||||
}
|
||||
|
||||
// Set start and end dates
|
||||
result.StartDate = startDate;
|
||||
result.EndDate = endDate;
|
||||
|
||||
if (save)
|
||||
{
|
||||
_backtestRepository.InsertBacktest(result);
|
||||
_backtestRepository.InsertBacktestForUser(user, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance)
|
||||
public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
|
||||
{
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, false);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
await bot.LoadAccount();
|
||||
|
||||
var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
|
||||
moneyManagement);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
result.User = user;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance)
|
||||
public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement,
|
||||
Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
|
||||
{
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, false);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
await bot.LoadAccount();
|
||||
|
||||
var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
|
||||
moneyManagement);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
result.User = user;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -177,10 +236,8 @@ namespace Managing.Application.Backtesting
|
||||
maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime
|
||||
);
|
||||
|
||||
// Then calculate the score
|
||||
var score = BacktestScorer.CalculateTotalScore(scoringParams);
|
||||
|
||||
|
||||
var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles,
|
||||
bot.BotType, account.Name)
|
||||
{
|
||||
@@ -197,7 +254,6 @@ namespace Managing.Application.Backtesting
|
||||
Score = score
|
||||
};
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -252,16 +308,14 @@ namespace Managing.Application.Backtesting
|
||||
return strategiesValues;
|
||||
}
|
||||
|
||||
public IEnumerable<Backtest> GetBacktests()
|
||||
{
|
||||
return _backtestRepository.GetBacktests();
|
||||
}
|
||||
|
||||
public bool DeleteBacktest(string id)
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -275,8 +329,111 @@ namespace Managing.Application.Backtesting
|
||||
{
|
||||
try
|
||||
{
|
||||
_backtestRepository.DeleteAllBacktests();
|
||||
//_backtestRepository.DropCollection();
|
||||
// Since we no longer have a general DeleteAllBacktests method in the repository,
|
||||
// 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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -101,6 +101,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
public override async void Start()
|
||||
{
|
||||
base.Start();
|
||||
// Load account synchronously
|
||||
await LoadAccount();
|
||||
|
||||
if (!IsForBacktest)
|
||||
@@ -127,7 +128,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
Fee = TradingService.GetFee(Account, IsForBacktest);
|
||||
}
|
||||
|
||||
private async Task LoadAccount()
|
||||
public async Task LoadAccount()
|
||||
{
|
||||
var account = await AccountService.GetAccount(AccountName, false, true);
|
||||
if (account == null)
|
||||
@@ -187,7 +188,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
if (!IsForWatchingOnly)
|
||||
await ManagePositions();
|
||||
|
||||
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
SaveBackup();
|
||||
@@ -225,6 +226,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (!OptimizedCandles.Any(c => c.Date == candle.Date))
|
||||
{
|
||||
OptimizedCandles.Enqueue(candle);
|
||||
Candles.Add(candle);
|
||||
await UpdateSignals(OptimizedCandles);
|
||||
}
|
||||
}
|
||||
@@ -235,9 +237,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
|
||||
{
|
||||
var signal = TradingBox.GetSignal(candles.ToHashSet(), Strategies, Signals, Scenario.LoopbackPeriod);
|
||||
|
||||
if (signal == null) return;
|
||||
|
||||
signal.User = Account.User;
|
||||
await AddSignal(signal);
|
||||
}
|
||||
|
||||
@@ -246,8 +248,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
Signals.Add(signal);
|
||||
|
||||
if (!IsForBacktest)
|
||||
TradingService.InsertSignal(signal);
|
||||
// if (!IsForBacktest)
|
||||
// TradingService.InsertSignal(signal);
|
||||
|
||||
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
|
||||
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()))
|
||||
{
|
||||
OptimizedCandles.Enqueue(candle);
|
||||
Candles.Add(candle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +500,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
MoneyManagement,
|
||||
signal.Direction,
|
||||
Ticker,
|
||||
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot ,
|
||||
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
IsForBacktest,
|
||||
lastPrice,
|
||||
@@ -643,12 +646,23 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
try
|
||||
{
|
||||
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
|
||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
||||
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);
|
||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
||||
}
|
||||
|
||||
{
|
||||
Logger.LogInformation($"No need to cancel orders for {Ticker}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Todo handle exception from evm
|
||||
Logger.LogError(ex, "Error during cancelOrders");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +97,13 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public List<ITradingBot> GetActiveBots()
|
||||
{
|
||||
return _botTasks.Values
|
||||
var bots = _botTasks.Values
|
||||
.Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType))
|
||||
.Select(wrapper => wrapper.BotInstance as ITradingBot)
|
||||
.Where(bot => bot != null)
|
||||
.ToList();
|
||||
|
||||
return bots;
|
||||
}
|
||||
|
||||
public IEnumerable<BotBackup> GetSavedBots()
|
||||
@@ -158,7 +160,7 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
bot.Start();
|
||||
bot.LoadBackup(backupBot);
|
||||
return () => { };
|
||||
return () => { };
|
||||
}
|
||||
|
||||
public IBot CreateSimpleBot(string botName, Workflow workflow)
|
||||
@@ -227,12 +229,10 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
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)
|
||||
{
|
||||
bot.IsForWatchingOnly = !bot.IsForWatchingOnly;
|
||||
}
|
||||
tradingBot.ToggleIsForWatchOnly().Wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MediatR;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot.Commands
|
||||
@@ -13,6 +14,7 @@ namespace Managing.Application.ManageBot.Commands
|
||||
public string Scenario { get; internal set; }
|
||||
public string AccountName { get; internal set; }
|
||||
public string MoneyManagementName { get; internal set; }
|
||||
public User User { get; internal set; }
|
||||
|
||||
public StartBotCommand(BotType botType,
|
||||
string name,
|
||||
@@ -21,6 +23,7 @@ namespace Managing.Application.ManageBot.Commands
|
||||
Timeframe timeframe,
|
||||
string accountName,
|
||||
string moneyManagementName,
|
||||
User user,
|
||||
bool isForWatchingOnly = false)
|
||||
{
|
||||
BotType = botType;
|
||||
@@ -31,6 +34,7 @@ namespace Managing.Application.ManageBot.Commands
|
||||
IsForWatchingOnly = isForWatchingOnly;
|
||||
AccountName = accountName;
|
||||
MoneyManagementName = moneyManagementName;
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Managing.Application.ManageBot
|
||||
public Task<string> Handle(StartBotCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
BotStatus botStatus = BotStatus.Down;
|
||||
var moneyManagement = _moneyManagementService.GetMoneyMangement(request.MoneyManagementName).Result;
|
||||
var moneyManagement = _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName).Result;
|
||||
switch (request.BotType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Application.MoneyManagements;
|
||||
|
||||
@@ -18,42 +19,110 @@ public class MoneyManagementService : IMoneyManagementService
|
||||
_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)
|
||||
{
|
||||
request.User = user;
|
||||
await _settingsRepository.InsertMoneyManagement(request);
|
||||
return request;
|
||||
}
|
||||
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.TakeProfit = request.TakeProfit;
|
||||
moneyManagement.BalanceAtRisk = request.BalanceAtRisk;
|
||||
moneyManagement.Leverage = request.Leverage;
|
||||
moneyManagement.Timeframe = request.Timeframe;
|
||||
moneyManagement.User = user;
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
public IEnumerable<MoneyManagement> GetMoneyMangements()
|
||||
{
|
||||
return _settingsRepository.GetMoneyManagements();
|
||||
}
|
||||
|
||||
public async Task<MoneyManagement> GetMoneyMangement(string name)
|
||||
{
|
||||
return await _settingsRepository.GetMoneyManagement(name);
|
||||
}
|
||||
|
||||
public bool DeleteMoneyManagement(string name)
|
||||
public bool DeleteMoneyManagement(User user, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
_settingsRepository.DeleteMoneyManagement(name);
|
||||
try
|
||||
{
|
||||
// Try to use user-specific repository method first
|
||||
_settingsRepository.DeleteMoneyManagementByUser(user, name);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fall back to verifying user ownership before deletion
|
||||
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result;
|
||||
|
||||
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
|
||||
{
|
||||
throw new UnauthorizedAccessException("You do not have permission to delete this money management strategy.");
|
||||
}
|
||||
|
||||
_settingsRepository.DeleteMoneyManagement(name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -63,11 +132,22 @@ public class MoneyManagementService : IMoneyManagementService
|
||||
}
|
||||
}
|
||||
|
||||
public bool DeleteMoneyManagements()
|
||||
public bool DeleteMoneyManagements(User user)
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -5,6 +5,8 @@ using Managing.Domain.Strategies;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
using static Managing.Common.Enums;
|
||||
using System.Collections.Generic;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Application.Scenarios
|
||||
{
|
||||
@@ -186,5 +188,166 @@ namespace Managing.Application.Scenarios
|
||||
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.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -41,10 +42,6 @@ public class SettingsService : ISettingsService
|
||||
throw new Exception("Cannot delete all strategies");
|
||||
}
|
||||
|
||||
if (!_moneyManagementService.DeleteMoneyManagements())
|
||||
{
|
||||
throw new Exception("Cannot delete all money management settings");
|
||||
}
|
||||
|
||||
if (!SetupSettings())
|
||||
{
|
||||
@@ -58,10 +55,10 @@ public class SettingsService : ISettingsService
|
||||
{
|
||||
try
|
||||
{
|
||||
SetupMoneyManagementsSeed(Timeframe.FiveMinutes);
|
||||
SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
|
||||
SetupMoneyManagementsSeed(Timeframe.OneHour);
|
||||
SetupMoneyManagementsSeed(Timeframe.OneDay);
|
||||
// SetupMoneyManagementsSeed(Timeframe.FiveMinutes);
|
||||
// SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
|
||||
// SetupMoneyManagementsSeed(Timeframe.OneHour);
|
||||
// SetupMoneyManagementsSeed(Timeframe.OneDay);
|
||||
SetupScenariosSeed();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -73,20 +70,20 @@ public class SettingsService : ISettingsService
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void SetupMoneyManagementsSeed(Timeframe timeframe)
|
||||
{
|
||||
var moneyManagement = new MoneyManagement()
|
||||
{
|
||||
Timeframe = timeframe,
|
||||
BalanceAtRisk = 0.05m,
|
||||
Leverage = 1,
|
||||
StopLoss = 0.021m,
|
||||
TakeProfit = 0.042m,
|
||||
Name = $"{timeframe}-MediumRisk",
|
||||
};
|
||||
|
||||
await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
|
||||
}
|
||||
// private async void SetupMoneyManagementsSeed(Timeframe timeframe)
|
||||
// {
|
||||
// var moneyManagement = new MoneyManagement()
|
||||
// {
|
||||
// Timeframe = timeframe,
|
||||
// BalanceAtRisk = 0.05m,
|
||||
// Leverage = 1,
|
||||
// StopLoss = 0.021m,
|
||||
// TakeProfit = 0.042m,
|
||||
// Name = $"{timeframe}-MediumRisk",
|
||||
// };
|
||||
//
|
||||
// await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
|
||||
// }
|
||||
|
||||
private void SetupScenariosSeed()
|
||||
{
|
||||
@@ -190,4 +187,58 @@ public class SettingsService : ISettingsService
|
||||
period: 200);
|
||||
_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.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Trading;
|
||||
@@ -10,40 +13,51 @@ namespace Managing.Application.Trading;
|
||||
public class ClosePositionCommandHandler(
|
||||
IExchangeService exchangeService,
|
||||
IAccountService accountService,
|
||||
ITradingService tradingService)
|
||||
ITradingService tradingService,
|
||||
ILogger<ClosePositionCommandHandler> logger = null)
|
||||
: ICommandHandler<ClosePositionCommand, Position>
|
||||
{
|
||||
public async Task<Position> Handle(ClosePositionCommand request)
|
||||
{
|
||||
// Get Trade
|
||||
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
||||
if (request.Position == null)
|
||||
try
|
||||
{
|
||||
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
|
||||
// Get Trade
|
||||
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
||||
if (request.Position == null)
|
||||
{
|
||||
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
|
||||
return request.Position;
|
||||
}
|
||||
|
||||
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
|
||||
|
||||
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
|
||||
? request.ExecutionPrice.GetValueOrDefault()
|
||||
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
|
||||
|
||||
// Close market
|
||||
var closedPosition =
|
||||
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
|
||||
var closeRequestedOrders =
|
||||
isForPaperTrading || (await exchangeService.CancelOrder(account, request.Position.Ticker));
|
||||
|
||||
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
|
||||
{
|
||||
request.Position.Status = PositionStatus.Finished;
|
||||
request.Position.ProfitAndLoss =
|
||||
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
|
||||
request.Position.Open.Leverage);
|
||||
tradingService.UpdatePosition(request.Position);
|
||||
}
|
||||
|
||||
return request.Position;
|
||||
}
|
||||
|
||||
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
|
||||
|
||||
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
|
||||
? request.ExecutionPrice.GetValueOrDefault()
|
||||
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
|
||||
|
||||
// Close market
|
||||
var closedPosition =
|
||||
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
|
||||
var closeRequestedOrders =
|
||||
isForPaperTrading || (await exchangeService.CancelOrder(account, request.Position.Ticker));
|
||||
|
||||
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
|
||||
catch (Exception ex)
|
||||
{
|
||||
request.Position.Status = PositionStatus.Finished;
|
||||
request.Position.ProfitAndLoss =
|
||||
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
|
||||
request.Position.Open.Leverage);
|
||||
tradingService.UpdatePosition(request.Position);
|
||||
}
|
||||
// Log the error - regardless of the error type
|
||||
logger?.LogError(ex, "Error closing position: {Message}", ex.Message);
|
||||
|
||||
return request.Position;
|
||||
throw new Exception(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@ namespace Managing.Application.Trading
|
||||
var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false);
|
||||
if (!request.IsForPaperTrading)
|
||||
{
|
||||
var cancelOrderResult = await exchangeService.CancelOrder(account, request.Ticker);
|
||||
if (!cancelOrderResult)
|
||||
{
|
||||
throw new Exception($"Not able to close all orders for {request.Ticker}");
|
||||
}
|
||||
// var cancelOrderResult = await exchangeService.CancelOrder(account, request.Ticker);
|
||||
// if (!cancelOrderResult)
|
||||
// {
|
||||
// throw new Exception($"Not able to close all orders for {request.Ticker}");
|
||||
// }
|
||||
}
|
||||
|
||||
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
|
||||
@@ -47,63 +47,60 @@ namespace Managing.Application.Trading
|
||||
: tradingService.GetFee(account, request.IsForPaperTrading);
|
||||
|
||||
var expectedStatus = GetExpectedStatus(request);
|
||||
position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(
|
||||
() =>
|
||||
{
|
||||
var openPrice = request.IsForPaperTrading || request.Price.HasValue
|
||||
? request.Price.Value
|
||||
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
|
||||
// position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(async () => { });
|
||||
|
||||
var trade = exchangeService.OpenTrade(
|
||||
account,
|
||||
request.Ticker,
|
||||
request.Direction,
|
||||
openPrice,
|
||||
quantity,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.Limit,
|
||||
isForPaperTrading: request.IsForPaperTrading,
|
||||
currentDate: request.Date).Result;
|
||||
var openPrice = request.IsForPaperTrading || request.Price.HasValue
|
||||
? request.Price.Value
|
||||
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
|
||||
|
||||
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
||||
return trade;
|
||||
});
|
||||
// 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,
|
||||
request.Ticker,
|
||||
request.Direction,
|
||||
openPrice,
|
||||
quantity,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.Limit,
|
||||
isForPaperTrading: request.IsForPaperTrading,
|
||||
currentDate: request.Date,
|
||||
stopLossPrice: stopLossPrice, // Pass determined SL price
|
||||
takeProfitPrice: takeProfitPrice); // Pass determined TP price
|
||||
|
||||
if (IsOpenTradeHandled(position.Open.Status, account.Exchange) && !request.IgnoreSLTP.GetValueOrDefault())
|
||||
{
|
||||
var closeDirection = request.Direction == TradeDirection.Long
|
||||
? TradeDirection.Short
|
||||
: TradeDirection.Long;
|
||||
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
||||
position.Open = trade;
|
||||
|
||||
var closeDirection = request.Direction == TradeDirection.Long
|
||||
? TradeDirection.Short
|
||||
: TradeDirection.Long;
|
||||
|
||||
// Stop loss
|
||||
position.StopLoss = exchangeService.BuildEmptyTrade(
|
||||
request.Ticker,
|
||||
RiskHelpers.GetStopLossPrice(request.Direction, position.Open.Price, request.MoneyManagement),
|
||||
position.Open.Quantity,
|
||||
closeDirection,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.StopLoss,
|
||||
request.Date,
|
||||
TradeStatus.PendingOpen);
|
||||
// Stop loss - Use the determined price
|
||||
position.StopLoss = exchangeService.BuildEmptyTrade(
|
||||
request.Ticker,
|
||||
stopLossPrice, // Use determined SL price
|
||||
position.Open.Quantity,
|
||||
closeDirection,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.StopLoss,
|
||||
request.Date,
|
||||
TradeStatus.PendingOpen);
|
||||
|
||||
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
|
||||
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
|
||||
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
|
||||
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
|
||||
|
||||
// Take profit
|
||||
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
||||
request.Ticker,
|
||||
RiskHelpers.GetTakeProfitPrice(request.Direction, position.Open.Price, request.MoneyManagement),
|
||||
quantity,
|
||||
closeDirection,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.TakeProfit,
|
||||
request.Date,
|
||||
TradeStatus.PendingOpen);
|
||||
|
||||
position.TakeProfit1.Fee = TradingHelpers.GetFeeAmount(fee,
|
||||
position.TakeProfit1.Price * position.TakeProfit1.Quantity, account.Exchange);
|
||||
}
|
||||
// Take profit - Use the determined price
|
||||
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
||||
request.Ticker,
|
||||
takeProfitPrice, // Use determined TP price
|
||||
quantity,
|
||||
closeDirection,
|
||||
request.MoneyManagement.Leverage,
|
||||
TradeType.TakeProfit,
|
||||
request.Date,
|
||||
TradeStatus.PendingOpen);
|
||||
|
||||
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
|
||||
? position.Status
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -42,12 +43,19 @@ public class UserService : IUserService
|
||||
{
|
||||
var recoveredAddress = _evmManager.VerifySignature(signature, message);
|
||||
|
||||
if (!authorizedAddresses.Contains(recoveredAddress))
|
||||
// Verify message
|
||||
if (!message.Equals("KaigenTeamXCowchain"))
|
||||
{
|
||||
_logger.LogWarning($"Address {recoveredAddress} not authorized");
|
||||
throw new Exception("Address not authorized");
|
||||
_logger.LogWarning($"Message {message} not starting with KaigenTeamXCowchain");
|
||||
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))
|
||||
{
|
||||
_logger.LogWarning($"Address {recoveredAddress} not corresponding");
|
||||
@@ -55,50 +63,49 @@ public class UserService : IUserService
|
||||
}
|
||||
|
||||
// Check if account exist
|
||||
var account = await _accountService.GetAccountByKey(recoveredAddress, true, false);
|
||||
var user = await _userRepository.GetUserByNameAsync(name);
|
||||
|
||||
if (account != null && user != null)
|
||||
if (user != null)
|
||||
{
|
||||
if (!user.Name.Equals(name))
|
||||
throw new Exception("Name not corresponding");
|
||||
|
||||
// User and account found
|
||||
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
||||
|
||||
if (!user.Name.Equals(name))
|
||||
throw new Exception("Name not corresponding");
|
||||
// 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No account and no
|
||||
account = new Account
|
||||
// First login
|
||||
user = new User
|
||||
{
|
||||
Name = $"Auth-{new Random().Next(1, 99)}",
|
||||
Key = recoveredAddress,
|
||||
Secret = "",
|
||||
Exchange = Common.Enums.TradingExchanges.Evm,
|
||||
Type = Common.Enums.AccountType.Auth,
|
||||
Name = name
|
||||
};
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_ = await _accountService.CreateAccount(user, account);
|
||||
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
||||
return user;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No user found, we create one
|
||||
// Create user if not existing
|
||||
user = new User()
|
||||
{
|
||||
Name = name,
|
||||
Accounts = new List<Account> { account },
|
||||
};
|
||||
await _userRepository.InsertUserAsync(user);
|
||||
|
||||
_ = await _accountService.CreateAccount(user, account);
|
||||
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;
|
||||
|
||||
@@ -135,6 +135,10 @@ public static class ApiBootstrap
|
||||
services.AddSingleton<IBotService, BotService>();
|
||||
services.AddSingleton<IWorkerService, WorkerService>();
|
||||
services.AddTransient<IPrivyService, PrivyService>();
|
||||
|
||||
// Web3Proxy Configuration
|
||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
||||
|
||||
// Stream
|
||||
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
|
||||
|
||||
@@ -6,14 +6,13 @@ namespace Managing.Domain.Accounts;
|
||||
|
||||
public class Account
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
[Required]
|
||||
public AccountType Type { get; set; }
|
||||
[Required] public string Name { get; set; }
|
||||
[Required] public TradingExchanges Exchange { get; set; }
|
||||
[Required] public AccountType Type { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Secret { get; set; }
|
||||
public User User { 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.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -29,6 +30,20 @@ public class Backtest
|
||||
Scenario = scenario;
|
||||
BotType = botType;
|
||||
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; }
|
||||
@@ -43,13 +58,15 @@ public class Backtest
|
||||
[Required] public Timeframe Timeframe { get; }
|
||||
[Required] public BotType BotType { 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 decimal Fees { get; set; }
|
||||
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
|
||||
[Required] public MoneyManagement OptimizedMoneyManagement { get; set; }
|
||||
[Required] public MoneyManagement MoneyManagement { get; set; }
|
||||
|
||||
[Required] public User User { get; set; }
|
||||
[Required] public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { 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
|
||||
{
|
||||
@@ -13,6 +15,7 @@ namespace Managing.Domain.Bots
|
||||
public string Name { get; set; }
|
||||
public int Interval { get; set; }
|
||||
public BotStatus Status { get; set; }
|
||||
public User User { get; set; }
|
||||
private CancellationTokenSource CancellationToken { get; set; }
|
||||
|
||||
public Bot(string name)
|
||||
@@ -22,6 +25,7 @@ namespace Managing.Domain.Bots
|
||||
Status = BotStatus.Down;
|
||||
CancellationToken = new CancellationTokenSource();
|
||||
ExecutionCount = 0;
|
||||
Interval = 3000;
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.MoneyManagements
|
||||
@@ -17,6 +18,8 @@ namespace Managing.Domain.MoneyManagements
|
||||
public decimal TakeProfit { get; set; }
|
||||
[Required]
|
||||
public decimal Leverage { get; set; }
|
||||
|
||||
public User User { get; set; }
|
||||
|
||||
public void FormatPercentage()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Domain.Scenarios
|
||||
{
|
||||
@@ -14,6 +15,7 @@ namespace Managing.Domain.Scenarios
|
||||
public string Name { get; set; }
|
||||
public List<Strategy> Strategies { get; set; }
|
||||
public int? LoopbackPeriod { get; set; }
|
||||
public User User { get; set; }
|
||||
|
||||
public void AddStrategy(Strategy strategy)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class TradingBox
|
||||
// Ensure limitedCandles is ordered chronologically
|
||||
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();
|
||||
|
||||
if (!candleLoopback.Any())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies
|
||||
@@ -18,9 +19,10 @@ namespace Managing.Domain.Strategies
|
||||
[Required] public TradingExchanges Exchange { get; set; }
|
||||
[Required] public StrategyType StrategyType { 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,
|
||||
TradingExchanges exchange, StrategyType strategyType, SignalType signalType)
|
||||
TradingExchanges exchange, StrategyType strategyType, SignalType signalType, User user = null)
|
||||
{
|
||||
Direction = direction;
|
||||
Confidence = confidence;
|
||||
@@ -30,6 +32,7 @@ namespace Managing.Domain.Strategies
|
||||
Exchange = exchange;
|
||||
Status = SignalStatus.WaitingForPosition;
|
||||
StrategyType = strategyType;
|
||||
User = user;
|
||||
|
||||
Identifier = $"{StrategyType}-{direction}-{ticker}-{candle?.Close}-{date:yyyyMMdd-HHmmss}";
|
||||
SignalType = signalType;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies
|
||||
@@ -14,6 +15,7 @@ namespace Managing.Domain.Strategies
|
||||
Name = name;
|
||||
Type = type;
|
||||
SignalType = ScenarioHelpers.GetSignalType(type);
|
||||
Candles = new FixedSizeQueue<Candle>(500);
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
@@ -29,6 +31,7 @@ namespace Managing.Domain.Strategies
|
||||
public int? SmoothPeriods { get; set; }
|
||||
public int? StochPeriods { get; set; }
|
||||
public int? CyclePeriods { get; set; }
|
||||
public User User { get; set; }
|
||||
|
||||
public virtual List<Signal> Run()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -43,6 +44,7 @@ namespace Managing.Domain.Trades
|
||||
public string Identifier { get; set; }
|
||||
[Required]
|
||||
public PositionInitiator Initiator { get; }
|
||||
public User User { get; set; }
|
||||
|
||||
public bool IsFinished()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Infrastructure.Databases.MongoDb;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
@@ -15,24 +16,54 @@ public class BacktestRepository : IBacktestRepository
|
||||
_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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
TradingExchanges exchange,
|
||||
Timeframe timeframe,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Managing.Infrastructure.Databases.MongoDb.Attributes;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
||||
using Exilion.TradingAtomics;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
||||
@@ -19,8 +20,13 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
||||
public RiskLevel RiskLevel { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public List<CandleDto> Candles { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
public BotType BotType { get; set; }
|
||||
public MoneyManagementDto MoneyManagement { 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 Leverage { get; 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 MoneyManagementDto MoneyManagement { 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 List<StrategyDto> Strategies { 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 StrategyType Type { 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 Timeframe Timeframe { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int MinimumHistory { get; set; }
|
||||
public int? Period { get; set; }
|
||||
public int? FastPeriods { get; set; }
|
||||
public int? SlowPeriods { get; set; }
|
||||
@@ -19,5 +20,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
||||
public int? SmoothPeriods { get; set; }
|
||||
public int? CyclePeriods { 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.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
@@ -12,6 +13,8 @@ using Managing.Domain.Workers;
|
||||
using Managing.Domain.Workflows.Synthetics;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
using static Managing.Common.Enums;
|
||||
using MongoDB.Bson;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb;
|
||||
|
||||
@@ -126,47 +129,63 @@ public static class MongoMappers
|
||||
|
||||
internal static Backtest Map(BacktestDto b)
|
||||
{
|
||||
return new Backtest(
|
||||
ticker: b.Ticker,
|
||||
scenario: b.Scenario,
|
||||
positions: b.Positions.ConvertAll(bPosition => Map(bPosition)),
|
||||
signals: b.Signals != null ? b.Signals.ConvertAll(bSignal => Map(bSignal)) : null,
|
||||
timeframe: b.Timeframe,
|
||||
candles: b.Candles.ConvertAll(bCandle => Map(bCandle)),
|
||||
accountName: b.AccountName,
|
||||
botType: b.BotType)
|
||||
if (b == null)
|
||||
return null;
|
||||
|
||||
var bTest = new Backtest(
|
||||
b.Ticker,
|
||||
b.Scenario,
|
||||
b.Positions?.Select(p => Map(p)).ToList() ?? new List<Position>(),
|
||||
b.Signals?.Select(s => Map(s)).ToList() ?? new List<Signal>(),
|
||||
b.Timeframe,
|
||||
b.Candles?.Select(c => Map(c)).ToList() ?? new List<Candle>(),
|
||||
b.BotType,
|
||||
b.AccountName)
|
||||
{
|
||||
Id = b.Id.ToString(),
|
||||
FinalPnl = b.FinalPnl,
|
||||
WinRate = b.WinRate,
|
||||
GrowthPercentage = b.GrowthPercentage,
|
||||
HodlPercentage = b.HodlPercentage,
|
||||
Id = b.Id.ToString(),
|
||||
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)
|
||||
{
|
||||
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,
|
||||
WinRate = result.WinRate,
|
||||
GrowthPercentage = result.GrowthPercentage,
|
||||
HodlPercentage = result.HodlPercentage,
|
||||
Candles = Map(result.Candles),
|
||||
Positions = Map(result.Positions),
|
||||
Signals = result.Signals.Select(s => Map(s)).ToList(),
|
||||
Ticker = result.Ticker,
|
||||
Scenario = result.Scenario,
|
||||
AccountName = result.AccountName,
|
||||
BotType = result.BotType,
|
||||
Timeframe = result.Timeframe,
|
||||
MoneyManagement = Map(result.MoneyManagement),
|
||||
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
|
||||
@@ -237,7 +256,8 @@ public static class MongoMappers
|
||||
AccountName = position.AccountName,
|
||||
MoneyManagement = Map(position.MoneyManagement),
|
||||
Initiator = position.Initiator,
|
||||
Ticker = position.Ticker
|
||||
Ticker = position.Ticker,
|
||||
User = position.User != null ? Map(position.User) : null
|
||||
};
|
||||
|
||||
if (position.StopLoss != null)
|
||||
@@ -284,7 +304,8 @@ public static class MongoMappers
|
||||
ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss },
|
||||
Status = dto.Status,
|
||||
SignalIdentifier = dto.SignalIdentifier,
|
||||
Identifier = dto.Identifier
|
||||
Identifier = dto.Identifier,
|
||||
User = dto.User != null ? Map(dto.User) : null
|
||||
};
|
||||
|
||||
if (dto.StopLoss != null)
|
||||
@@ -327,26 +348,42 @@ public static class MongoMappers
|
||||
{
|
||||
return new SignalDto
|
||||
{
|
||||
Identifier = signal.Identifier,
|
||||
Direction = signal.Direction,
|
||||
Candle = Map(signal.Candle),
|
||||
Confidence = signal.Confidence,
|
||||
Date = signal.Date,
|
||||
Candle = Map(signal.Candle),
|
||||
Identifier = signal.Identifier,
|
||||
Ticker = signal.Ticker,
|
||||
Status = signal.Status,
|
||||
Timeframe = signal.Timeframe,
|
||||
Type = signal.StrategyType
|
||||
Type = signal.StrategyType,
|
||||
User = signal.User != null ? Map(signal.User) : null
|
||||
};
|
||||
}
|
||||
|
||||
internal static Signal Map(SignalDto bSignal)
|
||||
{
|
||||
return new Signal(ticker: bSignal.Ticker, direction: bSignal.Direction, confidence: bSignal.Confidence,
|
||||
candle: Map(bSignal.Candle), date: bSignal.Date, exchange: default,
|
||||
strategyType: bSignal.Type, signalType: bSignal.SignalType)
|
||||
var candle = Map(bSignal.Candle);
|
||||
var signal = new Signal(
|
||||
bSignal.Ticker,
|
||||
bSignal.Direction,
|
||||
bSignal.Confidence,
|
||||
candle,
|
||||
bSignal.Date,
|
||||
TradingExchanges.Binance, //TODO FIXME When the signal status is modified from controller
|
||||
bSignal.Type,
|
||||
bSignal.SignalType,
|
||||
bSignal.User != null ? Map(bSignal.User) : null)
|
||||
{
|
||||
Status = bSignal.Status
|
||||
};
|
||||
|
||||
if (bSignal.User != null)
|
||||
{
|
||||
signal.User = Map(bSignal.User);
|
||||
}
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -355,11 +392,15 @@ public static class MongoMappers
|
||||
|
||||
public static ScenarioDto Map(Scenario scenario)
|
||||
{
|
||||
return new ScenarioDto()
|
||||
if (scenario == null)
|
||||
return null;
|
||||
|
||||
return new ScenarioDto
|
||||
{
|
||||
Name = scenario.Name,
|
||||
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)
|
||||
{
|
||||
return new Scenario(d.Name)
|
||||
if (d == null)
|
||||
return null;
|
||||
|
||||
var scenario = new Scenario(d.Name, d.LoopbackPeriod)
|
||||
{
|
||||
Name = d.Name,
|
||||
Strategies = Map(d.Strategies).ToList(),
|
||||
LoopbackPeriod = d.LoopbackPeriod > 0 ? d.LoopbackPeriod : 1
|
||||
Strategies = d.Strategies.Select(s => Map(s)).ToList(),
|
||||
User = d.User != null ? Map(d.User) : null
|
||||
};
|
||||
return scenario;
|
||||
}
|
||||
|
||||
private static List<StrategyDto> Map(List<Strategy> strategies)
|
||||
@@ -385,8 +429,13 @@ public static class MongoMappers
|
||||
|
||||
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,
|
||||
FastPeriods = strategyDto.FastPeriods,
|
||||
SlowPeriods = strategyDto.SlowPeriods,
|
||||
@@ -395,64 +444,31 @@ public static class MongoMappers
|
||||
SmoothPeriods = strategyDto.SmoothPeriods,
|
||||
StochPeriods = strategyDto.StochPeriods,
|
||||
CyclePeriods = strategyDto.CyclePeriods,
|
||||
SignalType = strategyDto.SignalType
|
||||
User = strategyDto.User != null ? Map(strategyDto.User) : null
|
||||
};
|
||||
}
|
||||
|
||||
internal static StrategyDto Map(Strategy strategy)
|
||||
{
|
||||
var dto = new StrategyDto
|
||||
if (strategy == null)
|
||||
return null;
|
||||
|
||||
return new StrategyDto
|
||||
{
|
||||
Type = strategy.Type,
|
||||
Name = strategy.Name,
|
||||
Type = strategy.Type,
|
||||
SignalType = strategy.SignalType,
|
||||
CyclePeriods = strategy.CyclePeriods,
|
||||
FastPeriods = strategy.FastPeriods,
|
||||
Multiplier = strategy.Multiplier,
|
||||
MinimumHistory = strategy.MinimumHistory,
|
||||
Period = strategy.Period,
|
||||
SignalPeriods = strategy.SignalPeriods,
|
||||
FastPeriods = strategy.FastPeriods,
|
||||
SlowPeriods = strategy.SlowPeriods,
|
||||
SignalPeriods = strategy.SignalPeriods,
|
||||
Multiplier = strategy.Multiplier,
|
||||
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)
|
||||
@@ -474,7 +490,8 @@ public static class MongoMappers
|
||||
StopLoss = request.StopLoss,
|
||||
TakeProfit = request.TakeProfit,
|
||||
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,
|
||||
TakeProfit = request.TakeProfit,
|
||||
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.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Infrastructure.Databases.MongoDb;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
@@ -50,4 +51,49 @@ public class SettingsRepository : ISettingsRepository
|
||||
dto.Id = mm.Id;
|
||||
_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.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Infrastructure.Databases.MongoDb;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
@@ -100,23 +101,98 @@ public class TradingRepository : ITradingRepository
|
||||
|
||||
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)
|
||||
{
|
||||
_scenarioRepository.CreateIndex(nameof(Scenario.Name));
|
||||
_scenarioRepository.InsertOne(MongoMappers.Map(scenario));
|
||||
// Check if scenario already exists for the same user
|
||||
var existingScenario = _scenarioRepository.FindOne(s =>
|
||||
s.Name == scenario.Name &&
|
||||
(scenario.User == null || (s.User != null && s.User.Name == scenario.User.Name)));
|
||||
|
||||
if (existingScenario != null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Scenario with name '{scenario.Name}' already exists for user '{scenario.User?.Name}'");
|
||||
}
|
||||
|
||||
var strategyDtos = new List<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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_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)
|
||||
@@ -133,9 +209,41 @@ public class TradingRepository : ITradingRepository
|
||||
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)
|
||||
|
||||
@@ -25,4 +25,19 @@ public class UserRepository : IUserRepository
|
||||
{
|
||||
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 isForPaperTrading = false,
|
||||
DateTime? currentDate = null,
|
||||
bool ioc = true);
|
||||
bool ioc = true,
|
||||
decimal? stopLossPrice = null,
|
||||
decimal? takeProfitPrice = null);
|
||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||
decimal GetPrice(Account account, Ticker ticker, DateTime date);
|
||||
|
||||
@@ -37,10 +37,12 @@ namespace Managing.Infrastructure.Exchanges
|
||||
bool reduceOnly = false,
|
||||
bool isForPaperTrading = false,
|
||||
DateTime? currentDate = null,
|
||||
bool ioc = true)
|
||||
bool ioc = true,
|
||||
decimal? stopLossPrice = null,
|
||||
decimal? takeProfitPrice = null)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
@@ -50,7 +52,7 @@ namespace Managing.Infrastructure.Exchanges
|
||||
|
||||
var processor = GetProcessor(account);
|
||||
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)
|
||||
@@ -205,6 +207,13 @@ namespace Managing.Infrastructure.Exchanges
|
||||
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)
|
||||
{
|
||||
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<List<Trade>> GetTrades(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 Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||
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)
|
||||
{
|
||||
var binanceResult = await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
|
||||
var binanceResult =
|
||||
await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
|
||||
return binanceResult.Success;
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ public class BinanceProcessor : BaseProcessor
|
||||
{
|
||||
balance += item.AvailableBalance;
|
||||
}
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
@@ -54,12 +56,15 @@ public class BinanceProcessor : BaseProcessor
|
||||
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),
|
||||
BinanceHelpers.Map(timeframe), startDate);
|
||||
var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync(
|
||||
BinanceHelpers.ToBinanceTicker(ticker),
|
||||
BinanceHelpers.Map(timeframe), startDate);
|
||||
|
||||
return (List<Candle>)binanceCandles.Data.Select(binanceKline => BinanceHelpers.Map(binanceKline, ticker, account.Exchange));
|
||||
return (List<Candle>)binanceCandles.Data.Select(binanceKline =>
|
||||
BinanceHelpers.Map(binanceKline, ticker, account.Exchange));
|
||||
}
|
||||
|
||||
public override decimal GetFee(Account account, bool isForPaperTrading = false)
|
||||
@@ -79,7 +84,8 @@ public class BinanceProcessor : BaseProcessor
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -90,7 +96,9 @@ public class BinanceProcessor : BaseProcessor
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -107,7 +115,7 @@ public class BinanceProcessor : BaseProcessor
|
||||
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||
{
|
||||
var binanceOrder =
|
||||
await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
|
||||
await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
|
||||
return (List<Trade>)binanceOrder.Data.Select(o => BinanceHelpers.Map(o));
|
||||
}
|
||||
|
||||
@@ -122,9 +130,14 @@ public class BinanceProcessor : BaseProcessor
|
||||
_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.SetPrice(price, GetPricePrecision(account, ticker));
|
||||
|
||||
@@ -158,6 +171,7 @@ public class BinanceProcessor : BaseProcessor
|
||||
trade.SetExchangeOrderId("");
|
||||
trade.SetMessage("");
|
||||
}
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
@@ -172,7 +186,8 @@ public class BinanceProcessor : BaseProcessor
|
||||
private int GetQuantityPrecision(Account account, Ticker ticker)
|
||||
{
|
||||
var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data;
|
||||
var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker)).QuantityPrecision;
|
||||
var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker))
|
||||
.QuantityPrecision;
|
||||
return Convert.ToInt32(precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,8 @@ public class EvmProcessor : BaseProcessor
|
||||
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);
|
||||
}
|
||||
@@ -119,7 +120,9 @@ public class EvmProcessor : BaseProcessor
|
||||
bool reduceOnly = false,
|
||||
bool isForPaperTrading = false,
|
||||
DateTime? currentDate = null,
|
||||
bool ioc = true)
|
||||
bool ioc = true,
|
||||
decimal? stopLossPrice = null,
|
||||
decimal? takeProfitPrice = null)
|
||||
{
|
||||
Trade trade;
|
||||
if (reduceOnly)
|
||||
@@ -128,7 +131,8 @@ public class EvmProcessor : BaseProcessor
|
||||
or TradeType.StopLoss)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
@@ -140,24 +144,23 @@ public class EvmProcessor : BaseProcessor
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public override async Task<List<Trade>> GetOrders(Account account, Ticker ticker)
|
||||
{
|
||||
return await _evmManager.GetOrders(account, ticker);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Not implemented
|
||||
|
||||
public override void LoadClient(Account account)
|
||||
{
|
||||
{
|
||||
// No client needed
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -175,4 +178,4 @@ public class EvmProcessor : BaseProcessor
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ public class FtxProcessor : BaseProcessor
|
||||
{
|
||||
balance += item.UsdValue;
|
||||
}
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ public class FtxProcessor : BaseProcessor
|
||||
LoadClient(account);
|
||||
|
||||
var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
|
||||
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
|
||||
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
|
||||
if (ftxKlines != null && ftxKlines.Any())
|
||||
{
|
||||
var lastCandle = ftxKlines.ToList().LastOrDefault();
|
||||
@@ -71,16 +72,18 @@ public class FtxProcessor : BaseProcessor
|
||||
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);
|
||||
|
||||
var candles = new List<Candle>();
|
||||
var ftxCandles = await _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
|
||||
FtxHelpers.Map(timeframe), startDate);
|
||||
FtxHelpers.Map(timeframe), startDate);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -96,11 +99,12 @@ public class FtxProcessor : BaseProcessor
|
||||
{
|
||||
LoadClient(account);
|
||||
var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker),
|
||||
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
|
||||
FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data;
|
||||
if (ftxKlines != null && ftxKlines.Any())
|
||||
{
|
||||
return ftxKlines.ToList().LastOrDefault().ClosePrice;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -126,29 +130,36 @@ public class FtxProcessor : BaseProcessor
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 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 ftxResult = await _ftxClient.TradeApi.Trading.PlaceTriggerOrderAsync(FtxHelpers.ToFtxTicker(ticker),
|
||||
direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy,
|
||||
ftxTriggerOrderType,
|
||||
triggerPrice: price,
|
||||
reduceOnly: true,
|
||||
retryUntilFilled: false,
|
||||
quantity: quantity);
|
||||
direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy,
|
||||
ftxTriggerOrderType,
|
||||
triggerPrice: price,
|
||||
reduceOnly: true,
|
||||
retryUntilFilled: false,
|
||||
quantity: quantity);
|
||||
_logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult));
|
||||
ftxOrder = FtxHelpers.Map(ftxResult, leverage);
|
||||
}
|
||||
@@ -177,7 +188,8 @@ public class FtxProcessor : BaseProcessor
|
||||
public override Orderbook GetOrderbook(Account account, Ticker ticker)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -200,4 +212,4 @@ public class FtxProcessor : BaseProcessor
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class KrakenProcessor : BaseProcessor
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override Task<bool> CancelOrder(Account account, Ticker ticker)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -48,7 +49,8 @@ public class KrakenProcessor : BaseProcessor
|
||||
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();
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public class KrakenProcessor : BaseProcessor
|
||||
{
|
||||
LoadClient(account);
|
||||
var krakenKline = _krakenClient.SpotApi.ExchangeData.GetKlinesAsync(ticker.ToString(),
|
||||
Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0];
|
||||
Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0];
|
||||
return (krakenKline.HighPrice + krakenKline.ClosePrice) / 2;
|
||||
}
|
||||
|
||||
@@ -120,10 +122,15 @@ public class KrakenProcessor : BaseProcessor
|
||||
_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);
|
||||
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.SetPrice(price, 1);
|
||||
|
||||
@@ -141,4 +148,4 @@ public class KrakenProcessor : BaseProcessor
|
||||
trade.SetMessage(krakenOrderExecuted.Message);
|
||||
return trade;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -262,9 +263,12 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||
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(
|
||||
accountName,
|
||||
await moneyManagementService.GetMoneyMangement(moneyManagement),
|
||||
await moneyManagementService.GetMoneyMangement(defaultUser, moneyManagement),
|
||||
direction,
|
||||
ticker,
|
||||
initiator,
|
||||
@@ -403,8 +407,21 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var builder = new ComponentBuilder();
|
||||
|
||||
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)
|
||||
{
|
||||
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