Update position worker (#11)

* Update position worker

* Update todo
This commit is contained in:
Oda
2025-02-20 12:06:15 +07:00
committed by GitHub
parent d7731dda0e
commit 160fd02aac
9 changed files with 199 additions and 133 deletions

View File

@@ -87,50 +87,11 @@
- [x] Create channel handler for address watcher - [x] Create channel handler for address watcher
- [x] Add input to specify the account balance. If not set, it will use the selected account balance - [x] Add input to specify the account balance. If not set, it will use the selected account balance
# The rest ## 4 - Trading desk [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=11070087)
## Part 1 : Adaptive trading - [x] Live data position hub
- [x] Live candles
- [x] From backtests list : Determine TP and Stop loss % by getting average profit and average lost for a signal - [x] Open position from desk
- [x] Modify bot to use MoneyManagement object instead of using the risk associated to the signal.
- [x] Bot object should return MoneyManagement object
- [ ] Strategy can return a suggested trade that will give the best orders to execute
## Part 2 : Workflows builder (Post-pawned)
- [x] Create a workflow type that will encapsulate a list of flows
- [x] Each flow will have parameters, inputs and outputs that will be used by the children flows
- [ ] Flow can handle multiple parents
- [ ] Run Simple bot base on a workflow
- [ ] Run backtest based on a workflow
- [ ] Add flows : ClosePosition, Scenario
## Part 2 : Strategies optimisation
- [ ] Optimize all strategies with more data (funding rate trend, bbwp, etc..)
- [ ] Optimize entry by getting last ask price and place an order limit to match directly with it
- [ ] Add bbwp indicator => PR
example : https://github.com/DaveSkender/Stock.Indicators/pull/893/files#diff-df1cd2a99846f7328c9fc3db4d2c164380a0fa943cdcad2ece01b886cac68137
- [ ] Create strategies types from signal (a divergence) / trend (stoch long or short) / context (high vol/low vol)
- [ ] Create a trend strategy on fundinrate -> long trend if negativ fr / short trend if positiv fr
- [ ] Add entry/exit price to signal for adaptive risk
- [ ] MACD: Open only when candle is not a big move to avoid late entry (only enter when low vol)
- [ ] Improve strategy composability by using delegate pattern
## Part 3 : Backtests automation
- [ ] Create a worker that determine best TP and SL
- [ ] Create a page 'Analyze' to show the best TP and SL for multiple backtest result
- [ ] Implement reverseSignal to close and flip the trade
- [ ] Load signal from database
- [ ] Create a bot to update signal into database that still in pending
- [ ] Signal return best SL and TP
- [ ] Combine multi-timeframe to detect better signals => Adapt risk on the lowest timeframe based on upper timeframe
- [ ] Remove candles from the backtestResult
- [ ] Save start/end time and timeframe into the result
- [ ] On GET backtest -> Retrieve the candle from cache or database
_______________________________________________________________________________________________________________
# Phase 2 - Web3 # Phase 2 - Web3
@@ -141,29 +102,47 @@ ________________________________________________________________________________
- [x] Close trade - [x] Close trade
- [ ] Update position / TP / SL - [ ] Update position / TP / SL
## 0 - GMX v2 ## Bots
## 1 - Wallet management - [x] Add button to display money management use by the bot
- [ ] POST POWNER - On the modarl, When simple bot selected, show only a select for the workflow
- [ ] Create onboarding process for new wallet ## Workflow
- [ ] User can deposit token to and evm account with a QR code or clicking on button to copy the address
- [ ] Add account abstraction to level up security
## 2 - [Track address](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32158941) - [x] List all workflow saved in
- [x] Use https://reactflow.dev/ to display a workflow (map flow to nodes and children to edges)
- [x] On update Nodes : https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js
- [x] Save workflow
- [ ] Reset workflow
- [ ] Add flows : Close Position, SendMessage
- [ ] On Flow.tsx : Display inputs/outputs names on the node
- [ ] Setup file tree UI for available flows : https://codesandbox.io/s/nlzui
- [x] Create a workflow type that will encapsulate a list of flows
- [x] Each flow will have parameters, inputs and outputs that will be used by the children flows
- [ ] Flow can handle multiple parents
- [ ] Run Simple bot base on a workflow
- [ ] Run backtest based on a workflow
- [ ] Add flows : ClosePosition, Scenario
- [ ] Create a snapshot of the account balances with BalancesWorker ## Backtests
- [ ] Display balance in $ evolution per token hold on the account page
## 3 - Settings [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159287) - [x] Display the money management used by the backtest
- [ ] Save theme into database ## Part 1 : Adaptive trading
- [ ] Call GET Settings/Theme to retrieve the theme used by the user
## 4 - Trading desk [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=11070087) - [x] From backtests list : Determine TP and Stop loss % by getting average profit and average lost for a signal
- [x] Modify bot to use MoneyManagement object instead of using the risk associated to the signal.
- [x] Bot object should return MoneyManagement object
- [x] Bot backup worker: Every x, get saved bots and check if still running. If not running call api to reboot bot.
- [x] Factorize amount management for GMX Service
- [x] Create worker to fetch the biggest spread between long\short funding rate and send alert when most profitable
delta neutral position is found
- [x] Create a worker to scrap funding fees over time (per ticker, per exchange)
-
- [x] Live data position hub ## Scenarios
- [x] Live candles
- [x] Open position from desk - [ ] Create a blocky-like editor to create scenarios https://google.github.io/blockly-samples/
## 5 - Live liquidation map ## 5 - Live liquidation map
@@ -172,31 +151,94 @@ ________________________________________________________________________________
- [ ] Live data signal - [ ] Live data signal
- [ ] Display massive liquidation on TradeChartWidget - [ ] Display massive liquidation on TradeChartWidget
## 6 - Metrics [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159107) ## To review
- [ ] ???? Improve strategy composability by using delegate pattern ????
- [ ] Post-powned - Load signal from database
- [ ] Create a bot to update signal into database that still in pending
- [ ] Signal return best SL and TP
- [ ] Extract BacktestReport from tests to a Tools projects
- [ ] Remove link betweend Domain object to EVM project
- [ ] GMX Service should handle only Nethereum object, not Domain.Models
- [ ] Compute backtests pagination on backend side
- [ ] Implement from/to tickers array pattern
_______________________________________________________________________________________________________________
# Before Kudai
## Part 1 : Strategies optimisation
- [ ] Create strategies types from signal (a divergence) / trend (stoch long or short) / context (high vol/low vol)
- [ ] Strategy can return a suggested trade that will give the best orders to execute
- [ ] Optimize all strategies with more data (funding rate trend, bbwp, etc..)
- [ ] Add entry/exit price to signal for adaptive risk
- [ ] Add bbwp indicator => PR
example : https://github.com/DaveSkender/Stock.Indicators/pull/893/files#diff-df1cd2a99846f7328c9fc3db4d2c164380a0fa943cdcad2ece01b886cac68137
- [ ] Optimize entry by getting last ask price and place an order limit t match directly with it
- [ ] Create a trend strategy on fundinrate -> long trend if negativ fr / short trend if positiv fr
- [ ] MACD: Open only when candle is not a big move to avoid late entry (only enter when low vol)
## Part 2 : Backtests automation
- [x] Save start/end time and timeframe into the result
- [ ] Create a worker that determine best TP and SL
- [ ] Create a page 'Analyze' to show the best TP and SL for multiple backtest result
- [ ] Implement reverseSignal to close and flip the trade
- [ ] Combine multi-timeframe to detect better signals => Adapt risk on the lowest timeframe based on upper timeframe
- [ ] Remove candles from the backtestResult
- [ ] On GET backtest -> Retrieve the candle from cache or database
- [ ] Create a worker that get top 5 cryptos by scenario - [ ] Create a worker that get top 5 cryptos by scenario
- [ ] Create worker to store portofolio evolution
- [ ] Display portofolio evolution ## Part 3 - Wallet management
- [ ] Create a worker to scrap funding fees over time (per ticker, per exchange)
- [ ] Create onboarding process for new wallet
- [ ] User can deposit token to and evm account with a QR code or clicking on button to copy the address
- [ ] Add account abstraction to level up security with privy
- [ ] Display balance in $ evolution per token hold on the account page
- [ ] Create a snapshot of the account balances with BalancesWorker
## 7 - Improvement [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159127) ## 7 - Improvement [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159127)
- [ ] Handling user on every object => Use the JWT token to inject the userName - [ ] Handling user on every object => Use the JWT token to inject the userName
- [ ] Extract BacktestReport from tests to a Tools projects
- [ ] Factorize amount management for GMX Service
- [ ] Remove link betweend Domain object to EVM project
- [ ] GMX Service should handle only Nethereum object, not Domain.Models
- [ ] Compute backtests pagination on backend side
- [x] Store webapp into docker
- [ ] Make possible to force close all positionpositions of a bot - [ ] Make possible to force close all positionpositions of a bot
- [ ] Save bot config to easily relaunch a bot and also analyze data
- [ ] Bot can handle limit order position -> fetch if order is still open then check if position of open - [ ] Bot can handle limit order position -> fetch if order is still open then check if position of open
- [ ] Create method to update the money management use by the bot - [ ] Create method to update the money management use by the bot
- [ ] Implement from/to tickers array pattern
- [ ] Extract all managing trade method into a TradingBox class => Create composable trading bot type easily - [ ] Extract all managing trade method into a TradingBox class => Create composable trading bot type easily
- [ ] Bot backup worker: Every x, get saved bots and check if still running. If not running call api to reboot bot.
- [ ] Create worker to fetch the biggest spread between long\short funding rate and send alert when most profitable ## Backend :
delta neutral position is found
- [ ] Filter all objects by users using botpress profiles
- [ ] Create endpoint to calculate the total volume traded
- [ ] Integrate Privy APIs to sign tx
- [ ] Get wallet balanced through Privy
- [ ] Remove Timeframe for strategies
- [ ] Update bot scenarios => shouldn't close current open position
- [ ] Remove accountName from getTicker
- [ ] Add enddate to backtest
- [ ] Replace WatchOnly by PaperTrading
## Botpress :
- [ ] Manage agent profiles
- [ ] Setup chat workflow
- [ ] Setup bad behaviors
- [ ] Setup chat for: create wallet, create strategy
## N8N pipelines:
- [ ] Create wallet: Check funds, approve usdc
- [ ] Create strategy: Get indicators, tickers and money management infos and call Managing.API
- [ ] Agent performance update
## Infra :
- [ ] Install botpress and n8n
- [ ] Setup botpress chat and language interpretation
- [ ] Add Tyk API Gateway for rate limit between n8n and Managing API
- [ ] New database Postgre for N8N and Botpress (agent profiles, built strategies, stats)
_____________________________________________________________________________________________________________
# Front-end # Front-end
@@ -214,22 +256,14 @@ ________________________________________________________________________________
## Trading desk ## Trading desk
- [ ] WAIT - Create a button to open a hedge on option market
- [ ] Get global exposure ((total position / total balances *) 100) - [ ] Get global exposure ((total position / total balances *) 100)
- [ ] WAIT - Create a button to open a hedge on option market
- [ ] Display multiple tab to show different tickers - [ ] Display multiple tab to show different tickers
## Scenarios ## 3 - Settings [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159287)
- [x] Create a blocky-like editor to create scenarios https://google.github.io/blockly-samples/ - [ ] Save theme into database
- [ ] Call GET Settings/Theme to retrieve the theme used by the user
## Bots
- [ ] Add button to display money management use by the bot
- [ ] On the modal, When simple bot selected, show only a select for the workflow
## Backtests
- [x] Display the money management used by the backtest
## Technical front-end : ## Technical front-end :
@@ -237,26 +271,17 @@ ________________________________________________________________________________
- [ ] Make Get Accounts List as a store - [ ] Make Get Accounts List as a store
- [ ] Replace all html-tag Modals (ex: BacktestPlayground.tsx) - [ ] Replace all html-tag Modals (ex: BacktestPlayground.tsx)
- [ ] Refactoring Components : - [ ] Refactoring Components :
- organism : Put all the code related to the app (Trade charts etc). This folder should be delete when the project is - organism : Put all the code related to the app (Trade charts etc). This folder should be delete when the project
forked is
- mollecules : Put aggregate of atoms to create forms/modal/table forked
- atoms : Put all basic html tag components (List, Select, Slider...) - mollecules : Put aggregate of atoms to create forms/modal/table
- atoms : Put all basic html tag components (List, Select, Slider...)
## Workflow
- [x] List all workflow saved in
- [x] Use https://reactflow.dev/ to display a workflow (map flow to nodes and children to edges)
- [x] On update Nodes : https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js
- [x] Save workflow
- [ ] Reset workflow
- [ ] Add flows : Close Position, SendMessage
- [ ] On Flow.tsx : Display inputs/outputs names on the node
- [ ] Setup file tree UI for available flows : https://codesandbox.io/s/nlzui
_____________________________________________________________________________________________________________ _____________________________________________________________________________________________________________
# Technicals # Technicals
- [x] Store webapp into docker
- [ ] Remove mediator - [ ] Remove mediator
- [ ] Check Traefik : https://www.cloudbees.com/blog/setting-up-traefik-as-a-reverse-proxy-for-asp-net-applications - [ ] Check Traefik : https://www.cloudbees.com/blog/setting-up-traefik-as-a-reverse-proxy-for-asp-net-applications
- [ ] Implement IHostedService/BackgroundService to run bot in background https://www.youtube.com/watch?v=fw-n2RkzOMI - [ ] Implement IHostedService/BackgroundService to run bot in background https://www.youtube.com/watch?v=fw-n2RkzOMI

View File

@@ -18,6 +18,7 @@ using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme;
// Builder // Builder
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:5001");
builder.Configuration.SetBasePath(AppContext.BaseDirectory); builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json"); .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");

View File

@@ -1,7 +1,10 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers; using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Managing.Domain.Accounts;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Newtonsoft.Json;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers; namespace Managing.Api.Workers.Workers;
@@ -13,13 +16,14 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
private readonly IExchangeService _exchangeService; private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly ILogger<PositionManagerWorker> _logger; private readonly ILogger<PositionManagerWorker> _logger;
private readonly ICacheService _cacheService;
public PositionManagerWorker( public PositionManagerWorker(
ILogger<PositionManagerWorker> logger, ILogger<PositionManagerWorker> logger,
IWorkerService workerService, IWorkerService workerService,
ITradingService tradingService, ITradingService tradingService,
IExchangeService exchangeService, IExchangeService exchangeService,
IAccountService accountService) : base( IAccountService accountService, ICacheService cacheService) : base(
_workerType, _workerType,
logger, logger,
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1),
@@ -29,6 +33,7 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
_tradingService = tradingService; _tradingService = tradingService;
_exchangeService = exchangeService; _exchangeService = exchangeService;
_accountService = accountService; _accountService = accountService;
_cacheService = cacheService;
} }
protected override async Task Run(CancellationToken cancellationToken) protected override async Task Run(CancellationToken cancellationToken)
@@ -46,21 +51,23 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
foreach (var position in positions) foreach (var position in positions)
{ {
_logger.LogInformation("Managing Partilly filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker); _logger.LogInformation("Managing Partilly filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}",
position.Identifier, position.Date, position.OriginDirection, position.Ticker);
var account = await _accountService.GetAccount(position.AccountName, false, false); var account = await _accountService.GetAccount(position.AccountName, false, false);
try try
{ {
if (position.StopLoss.Status == TradeStatus.PendingOpen) if (position.StopLoss.Status == TradeStatus.PendingOpen)
{ {
var stopLoss = _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection, position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow).Result; var stopLoss = await _exchangeService.OpenStopLoss(account, position.Ticker,
position.OriginDirection,
position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow);
if (stopLoss != null & (stopLoss.Status == TradeStatus.Requested)) if (stopLoss != null & (stopLoss.Status == TradeStatus.Requested))
{ {
position.StopLoss = stopLoss; position.StopLoss = stopLoss;
_logger.LogInformation("|_ SL is requested"); _logger.LogInformation("|_ SL is requested");
} }
else else
{ {
@@ -74,7 +81,9 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
if (position.TakeProfit1.Status == TradeStatus.PendingOpen) if (position.TakeProfit1.Status == TradeStatus.PendingOpen)
{ {
var takeProfit1 = _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection, position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow).Result; var takeProfit1 = await _exchangeService.OpenTakeProfit(account, position.Ticker,
position.OriginDirection, position.TakeProfit1.Price, position.TakeProfit1.Quantity, false,
DateTime.UtcNow);
if (takeProfit1 != null & (takeProfit1.Status == TradeStatus.Requested)) if (takeProfit1 != null & (takeProfit1.Status == TradeStatus.Requested))
{ {
@@ -94,7 +103,9 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
if (position.TakeProfit2 != null && if (position.TakeProfit2 != null &&
position.TakeProfit2.Status == TradeStatus.PendingOpen) position.TakeProfit2.Status == TradeStatus.PendingOpen)
{ {
var takeProfit2 = _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection, position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow).Result; var takeProfit2 = await _exchangeService.OpenTakeProfit(account, position.Ticker,
position.OriginDirection, position.TakeProfit2.Price, position.TakeProfit2.Quantity, false,
DateTime.UtcNow);
if (takeProfit2 != null & (takeProfit2.Status == TradeStatus.Requested)) if (takeProfit2 != null & (takeProfit2.Status == TradeStatus.Requested))
{ {
@@ -116,9 +127,9 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
_logger.LogError($"|_ Cannot fully filled position because : {ex.Message}"); _logger.LogError($"|_ Cannot fully filled position because : {ex.Message}");
} }
if (position.StopLoss.Status == TradeStatus.Requested TradeStatus[] validStatus = [TradeStatus.Requested, TradeStatus.Filled];
&& position.TakeProfit1.Status == TradeStatus.Requested if (validStatus.Contains(position.StopLoss.Status)
&& (position.TakeProfit2 == null || position.TakeProfit2.Status == TradeStatus.Requested)) && (validStatus.Contains(position.TakeProfit1.Status)))
{ {
position.Status = PositionStatus.Filled; position.Status = PositionStatus.Filled;
_logger.LogInformation($"|_ Position is now open and SL/TP are correctly requested"); _logger.LogInformation($"|_ Position is now open and SL/TP are correctly requested");
@@ -136,8 +147,9 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
foreach (var position in positions) foreach (var position in positions)
{ {
_logger.LogInformation("Managing filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker); _logger.LogInformation("Managing filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}",
var account = await _accountService.GetAccount(position.AccountName, false, false); position.Identifier, position.Date, position.OriginDirection, position.Ticker);
var account = await GetAccount(position.AccountName);
var updatedPosition = await _tradingService.ManagePosition(account, position); var updatedPosition = await _tradingService.ManagePosition(account, position);
_tradingService.UpdatePosition(updatedPosition); _tradingService.UpdatePosition(updatedPosition);
@@ -158,9 +170,10 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
foreach (var position in positions) foreach (var position in positions)
{ {
_logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker); _logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}",
position.Identifier, position.Date, position.OriginDirection, position.Ticker);
position.Status = PositionStatus.Updating; position.Status = PositionStatus.Updating;
_tradingService.UpdatePosition(position); _tradingService.UpdatePosition(position);
// Update status if position is open since to long // Update status if position is open since to long
if (position.Date < DateTime.UtcNow.AddDays(-2)) if (position.Date < DateTime.UtcNow.AddDays(-2))
@@ -171,14 +184,15 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
continue; continue;
} }
var account = await _accountService.GetAccount(position.AccountName, false, false); var account = await GetAccount(position.AccountName);
if (!(await _exchangeService.GetOpenOrders(account, position.Ticker)).Any()) var currentOpenOrders = await _exchangeService.GetOpenOrders(account, position.Ticker);
{ // if (currentOpenOrders.Any())
position.Status = PositionStatus.Canceled; // {
_tradingService.UpdatePosition(position); // position.Status = PositionStatus.Canceled;
_logger.LogInformation($"|_ Position is now Canceled - Position close from exchange"); // _tradingService.UpdatePosition(position);
continue; // _logger.LogInformation($"|_ Position is now Canceled - Position close from exchange");
} // continue;
// }
var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker); var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
@@ -198,4 +212,16 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
} }
} }
} private async Task<Account> GetAccount(string accountName)
{
var account = _cacheService.GetValue<Account>(accountName);
if (account == null)
{
account = await _accountService.GetAccount(accountName, false, false);
_cacheService.SaveValue(accountName, JsonConvert.SerializeObject(account));
}
return account;
}
}

View File

@@ -39,13 +39,14 @@ public class TradingController : ControllerBase
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler, ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler, ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
ITradingService tradingService, ITradingService tradingService,
IMediator mediator) IMediator mediator, IMoneyManagementService moneyManagementService)
{ {
_logger = logger; _logger = logger;
_openTradeCommandHandler = openTradeCommandHandler; _openTradeCommandHandler = openTradeCommandHandler;
_closeTradeCommandHandler = closeTradeCommandHandler; _closeTradeCommandHandler = closeTradeCommandHandler;
_tradingService = tradingService; _tradingService = tradingService;
_mediator = mediator; _mediator = mediator;
_moneyManagementService = moneyManagementService;
} }
/// <summary> /// <summary>
@@ -112,7 +113,7 @@ public class TradingController : ControllerBase
/// <param name="moneyManagement">The money management strategy details (optional).</param> /// <param name="moneyManagement">The money management strategy details (optional).</param>
/// <param name="openPrice">The opening price for the trade (optional).</param> /// <param name="openPrice">The opening price for the trade (optional).</param>
/// <returns>The opened position.</returns> /// <returns>The opened position.</returns>
[HttpGet("OpenPosition")] [HttpPost("OpenPosition")]
public async Task<ActionResult<Position>> Trade( public async Task<ActionResult<Position>> Trade(
string accountName, string accountName,
string moneyManagementName, string moneyManagementName,
@@ -120,7 +121,7 @@ public class TradingController : ControllerBase
Ticker ticker, Ticker ticker,
RiskLevel riskLevel, RiskLevel riskLevel,
bool isForPaperTrading, bool isForPaperTrading,
MoneyManagement? moneyManagement = null, MoneyManagement? moneyManagement = null,
decimal? openPrice = null) decimal? openPrice = null)
{ {
if (string.IsNullOrEmpty(accountName)) if (string.IsNullOrEmpty(accountName))
@@ -130,10 +131,11 @@ public class TradingController : ControllerBase
if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null) if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null)
{ {
throw new ArgumentException($"'{nameof(moneyManagementName)}' cannot be null or empty.", nameof(moneyManagementName)); throw new ArgumentException($"'{nameof(moneyManagementName)}' cannot be null or empty.",
nameof(moneyManagementName));
} }
if (moneyManagement == null) if (moneyManagement != null)
{ {
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName); moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName);
} }

View File

@@ -308,11 +308,14 @@ public class TradingBot : Bot, ITradingBot
{ {
await HandleClosedPosition(positionForSignal); await HandleClosedPosition(positionForSignal);
} }
else if (position.Status == PositionStatus.Filled) else if (position.Status == (PositionStatus.Filled | PositionStatus.PartiallyFilled))
{ {
// For backtesting or force close if not executed on exchange : // For backtesting or force close if not executed on exchange :
// check if position is still open // check if position is still open
// Check status, if still open update the status of the position // Check status, if still open update the status of the position
// Position might be partially filled, meaning that TPSL havent been sended yet
// But the position might already been closed by the exchange so we have to check should be closed
var lastCandle = IsForBacktest var lastCandle = IsForBacktest
? OptimizedCandles.Last() ? OptimizedCandles.Last()
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);

View File

@@ -618,7 +618,7 @@ public class EvmManager : IEvmManager
{ {
var chain = ChainService.GetChain(chainName); var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl); var web3 = new Web3(chain.RpcUrl);
var quantity = await GmxService.QuantityInPosition(web3, publicAddress, ticker); var quantity = await _gmxV2Service.QuantityInPosition(web3, publicAddress, ticker);
return quantity; return quantity;
} }

View File

@@ -1372,4 +1372,9 @@ public class GmxV2Service
{ {
return $"{account}-{market}-{collateralToken}-{(isLong ? "true" : "false")}"; return $"{account}-{market}-{collateralToken}-{(isLong ? "true" : "false")}";
} }
public async Task<decimal> QuantityInPosition(Web3 web3, string publicAddress, Enums.Ticker ticker)
{
return (await GetTrade(web3, publicAddress, ticker)).Status == Enums.TradeStatus.Filled ? 1 : 0;
}
} }

View File

@@ -9,7 +9,11 @@ const loadWidget = (component: any) => {
const DeskWidget = React.forwardRef((props: any, ref) => { const DeskWidget = React.forwardRef((props: any, ref) => {
const widget = props const widget = props
const widgetProperties = widget['data-properties'] as IWidgetProperties const widgetProperties = widget['data-properties'] as IWidgetProperties
const WidgetComponent = loadWidget(widgetProperties.id)
if (!widgetProperties) {
return null
}
const WidgetComponent = loadWidget(widgetProperties?.id)
return ( return (
<div ref={ref} {...props}> <div ref={ref} {...props}>
<Suspense fallback={<>Loading</>}> <Suspense fallback={<>Loading</>}>

View File

@@ -47,7 +47,7 @@ const OpenPositonWidget = () => {
form.riskLevel, form.riskLevel,
false, false,
form.stopLoss, form.stopLoss,
form.takeProfit undefined
) )
.then((result) => { .then((result) => {
t.update('success', 'Position created') t.update('success', 'Position created')