Filter everything with users (#16)

* Filter everything with users

* Fix backtests and user management

* Add cursor rules

* Fix backtest and bots

* Update configs names

* Sign until unauth

* Setup delegate

* Setup delegate and sign

* refact

* Enhance Privy signature generation with improved cryptographic methods

* Add Fastify backend

* Add Fastify backend routes for privy

* fix privy signing

* fix privy client

* Fix tests

* add gmx core

* fix merging sdk

* Fix tests

* add gmx core

* add gmx core

* add privy to boilerplate

* clean

* fix

* add fastify

* Remove Managing.Fastify submodule

* Add Managing.Fastify as regular directory instead of submodule

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

* Add token approval functionality to Privy plugin

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

* Add PrivyApproveTokenResponse model for token approval response

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

* Refactor trading commands and enhance API routes

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

* Enhance GMX trading functionality and update dependencies

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

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

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

* Refactor GMX plugin and enhance testing for position opening

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

* Update sdk

* Update

* update fastify

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

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

* Update package.json for Web3Proxy

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

* Update Dockerfile for Web3Proxy

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

* Refactor Dockerfile and package.json for Web3Proxy

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

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

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

* Add PrivyInitAddressResponse model for handling initialization responses

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

* Update

* Update

* Remove fastify-tsconfig installation from Dockerfile-web3proxy

* Add build step to Dockerfile-web3proxy

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

* Update

* approvals

* Open position from front embedded wallet

* Open position from front embedded wallet

* Open position from front embedded wallet

* Fix call contracts

* Fix limit price

* Close position

* Fix close position

* Fix close position

* add pinky

* Refactor position handling logic

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

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

* add cancel orders

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

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

* Cast transaction parameters as any type in approveTokenImpl for compatibility

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

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

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

* add referral code

* fix referral

* Add sltp

* Fix typo

* Fix typo

* setup sltp on backtend

* get orders

* get positions with slp

* fixes

* fixes close position

* fixes

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

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

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

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

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

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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");
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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:

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;