From 464a8730e8be98ebe591e005a1ca880ff9f6b556 Mon Sep 17 00:00:00 2001 From: alirehmani Date: Fri, 3 May 2024 16:39:25 +0500 Subject: [PATCH] docker files fixes from liaqat --- .dockerignore | 2 + .github/workflows/caprover.yml | 35 + .gitignore | 379 +++ Documentation.md | 61 + README.md | 129 + assets/NSwagConfig.nswag | 75 + assets/Todo-v1.txt | 151 ++ assets/Todo-v2.md | 199 ++ assets/documentation/Architecture.drawio | 1586 +++++++++++ assets/documentation/EndGame.md | 12 + assets/documentation/Flows.md | 27 + assets/documentation/PositionWorkflow.md | 25 + assets/img/Architecture.png | Bin 0 -> 203872 bytes assets/img/doc-docker.png | Bin 0 -> 20522 bytes assets/img/doc-influxdb-apikeys-1.png | Bin 0 -> 23868 bytes assets/img/doc-influxdb-apikeys-2.png | Bin 0 -> 41684 bytes assets/img/doc-influxdb.png | Bin 0 -> 30060 bytes assets/img/doc-settings.png | Bin 0 -> 6414 bytes captain-definition | 4 + scripts/build_and_run.sh | 13 + scripts/clean-front-end-code.cmd | 4 + scripts/docker-deploy-sandbox - Copy.cmd | 5 + scripts/docker-deploy-sandbox.cmd | 5 + scripts/docker-redeploy-oda.cmd | 22 + src/.dockerignore | 1 + src/.editorconfig | 4 + src/Managing.Api.Workers.csproj | 45 + .../Controllers/WorkerController.cs | 31 + src/Managing.Api.Workers/Dockerfile | 36 + .../Filters/EnumSchemaFilter.cs | 20 + .../Managing.Api.Workers.csproj | 45 + src/Managing.Api.Workers/Program.cs | 150 ++ .../Workers/BaseWorker.cs | 75 + src/Managing.Api.Workers/Workers/FeeWorker.cs | 29 + .../Workers/LeaderboardWorker.cs | 28 + .../Workers/NoobiesboardWorker.cs | 28 + .../Workers/PositionFetcher.cs | 37 + .../Workers/PositionManagerWorker.cs | 200 ++ .../Workers/PricesBaseWorker.cs | 40 + .../Workers/PricesFifteenMinutesWorker.cs | 23 + .../Workers/PricesFiveMinutesWorker.cs | 23 + .../Workers/PricesFourHoursWorker.cs | 23 + .../Workers/PricesOneDayWorker.cs | 23 + .../Workers/PricesOneHourWorker.cs | 22 + .../Workers/SpotlightWorker.cs | 34 + .../Workers/TopVolumeTickerWorker.cs | 28 + .../Workers/TraderWatcher.cs | 29 + .../appsettings.Development.json | 23 + .../appsettings.Khalid.json | 24 + .../appsettings.Lowpro.json | 39 + .../appsettings.Oda-docker.json | 24 + src/Managing.Api.Workers/appsettings.Oda.json | 24 + .../Authorization/JwtMiddleware.cs | 27 + src/Managing.Api/Authorization/JwtUtils.cs | 70 + .../Controllers/AccountController.cs | 58 + .../Controllers/BacktestController.cs | 133 + .../Controllers/BaseController.cs | 35 + src/Managing.Api/Controllers/BotController.cs | 168 ++ .../Controllers/DataController.cs | 73 + .../Controllers/MoneyManagementController.cs | 44 + .../Controllers/ScenarioController.cs | 83 + .../Controllers/SettingsController.cs | 30 + .../Controllers/TradingController.cs | 107 + .../Controllers/UserController.cs | 39 + .../Controllers/WorkflowController.cs | 45 + src/Managing.Api/Dockerfile | 36 + .../GlobalErrorHandlingMiddleware.cs | 62 + src/Managing.Api/Filters/EnumSchemaFilter.cs | 20 + src/Managing.Api/Managing.Api.csproj | 43 + .../Models/Requests/CreateScenarioRequest.cs | 12 + .../Models/Requests/CreateStrategyRequest.cs | 16 + .../Models/Requests/LoginRequest.cs | 15 + .../Models/Requests/RunBacktestRequest.cs | 15 + .../Models/Requests/StartBotRequest.cs | 25 + .../Models/Responses/TradingBot.cs | 45 + src/Managing.Api/Program.cs | 168 ++ src/Managing.Api/appsettings.Development.json | 13 + src/Managing.Api/appsettings.Khalid.json | 34 + src/Managing.Api/appsettings.Lowpro.json | 46 + src/Managing.Api/appsettings.Oda-Sandbox.json | 37 + src/Managing.Api/appsettings.Oda-docker.json | 36 + src/Managing.Api/appsettings.Oda.json | 34 + src/Managing.Api/appsettings.json | 48 + src/Managing.Api/captain-definition | 4 + src/Managing.Api/failures.txt | 19 + .../Managing.Application.Abstractions.csproj | 14 + .../Repositories/IAccountRepository.cs | 12 + .../Repositories/IBacktestRepository.cs | 12 + .../Repositories/ICandleRepository.cs | 18 + .../Repositories/IEvmManager.cs | 36 + .../Repositories/ISettingsRepository.cs | 13 + .../Repositories/IStatisticRepository.cs | 20 + .../Repositories/ITradingRepository.cs | 29 + .../Repositories/IUserRepository.cs | 9 + .../Repositories/IWorkerRepository.cs | 14 + .../Repositories/IWorkflowRepository.cs | 12 + .../Services/IAccountService.cs | 16 + .../Services/IBacktester.cs | 20 + .../Services/IDiscordService.cs | 19 + .../Services/IExchangeService.cs | 45 + .../Services/IExchangeStream.cs | 10 + .../Services/IMessengerService.cs | 19 + .../Services/IStreamService.cs | 7 + .../Services/ITickerService.cs | 8 + .../Services/ITradaoService.cs | 11 + .../Services/ITradingService.cs | 35 + .../Services/IUserService.cs | 9 + src/Managing.Application.Tests/BaseTests.cs | 51 + src/Managing.Application.Tests/BotsTests.cs | 408 +++ .../CandleHelpersTests.cs | 20 + .../Managing.Application.Tests.csproj | 35 + .../MathHelpersTests.cs | 24 + .../PositionTests.cs | 56 + .../ProfitAndLossTests.cs | 251 ++ .../RiskHelpersTests.cs | 62 + .../StrategyTests.cs | 212 ++ .../TradingBaseTests.cs | 85 + .../WorkflowTests.cs | 54 + .../Abstractions/IPricesService.cs | 8 + .../Abstractions/IStatisticService.cs | 19 + .../Abstractions/IWorkerService.cs | 15 + .../Managing.Application.Workers.csproj | 18 + .../PricesService.cs | 64 + .../StatisticService.cs | 304 +++ .../WorkerService.cs | 69 + .../Abstractions/IBotFactory.cs | 16 + .../Abstractions/ICacheService.cs | 12 + .../Abstractions/ICommandHandler.cs | 7 + .../Abstractions/IFlowFactory.cs | 9 + .../Abstractions/IMoneyManagementService.cs | 13 + .../Abstractions/IScenarioService.cs | 29 + .../Abstractions/ISettingsService.cs | 7 + .../Abstractions/ITaskCache.cs | 48 + .../Abstractions/ITradingBot.cs | 33 + .../Abstractions/IWorkflowService.cs | 12 + .../Accounts/AccountService.cs | 172 ++ .../Backtesting/Backtester.cs | 192 ++ .../Bots/Base/BotFactory.cs | 111 + src/Managing.Application/Bots/FlippingBot.cs | 49 + src/Managing.Application/Bots/ScalpingBot.cs | 48 + src/Managing.Application/Bots/SimpleBot.cs | 39 + src/Managing.Application/Bots/TradingBot.cs | 651 +++++ src/Managing.Application/Hubs/BacktestHub.cs | 15 + src/Managing.Application/Hubs/BotHub.cs | 17 + src/Managing.Application/Hubs/CandleHub.cs | 41 + src/Managing.Application/Hubs/PositionHub.cs | 12 + .../ManageBot/Commands/DeleteBotCommand.cs | 13 + .../Commands/GetActiveBotsCommand.cs | 12 + .../ManageBot/Commands/RestartBotCommand.cs | 14 + .../ManageBot/Commands/StartBotCommand.cs | 36 + .../ManageBot/Commands/StopBotCommand.cs | 17 + .../Commands/ToggleIsForWatchingCommand.cs | 17 + .../ManageBot/DeleteBotCommandHandler.cs | 32 + .../ManageBot/GetActiveBotsCommandHandler.cs | 30 + .../ManageBot/RestartBotCommandHandler.cs | 39 + .../ManageBot/StartBotCommandHandler.cs | 42 + .../ManageBot/StopBotCommandHandler.cs | 39 + .../ToggleIsForWatchingCommandHandler.cs | 23 + .../Managing.Application.csproj | 34 + .../MoneyManagementService.cs | 79 + .../Scenarios/ScenarioService.cs | 144 + .../IUnhandledExceptionBehaviour.cs | 9 + .../Behaviours/UnhandledExceptionBehaviour.cs | 31 + .../Shared/Behaviours/ValidationBehaviour.cs | 29 + .../Shared/MessengerService.cs | 66 + .../Shared/SettingsService.cs | 199 ++ .../Shared/StreamService.cs | 30 + .../Shared/TickerService.cs | 21 + .../Trading/ClosePositionCommandHandler.cs | 55 + .../Trading/Commands/ClosePositionCommand.cs | 17 + .../Trading/Commands/GetPositionsCommand.cs | 16 + .../Trading/Commands/GetTradeCommand.cs | 20 + .../Trading/Commands/GetTradesCommand.cs | 18 + .../Trading/Commands/OpenPositionRequest.cs | 48 + .../Trading/GetPositionsCommandHandler.cs | 23 + .../Trading/GetTradeCommandHandler.cs | 23 + .../Trading/GetTradesCommandHandler.cs | 25 + .../Trading/OpenPositionCommandHandler.cs | 128 + .../Trading/TradingPolicies.cs | 18 + .../Trading/TradingService.cs | 352 +++ src/Managing.Application/Users/UserService.cs | 88 + .../Workflows/FlowFactory.cs | 51 + .../Workflows/Flows/Feeds/FeedTicker.cs | 64 + .../Workflows/Flows/Strategies/RsiDiv.cs | 65 + .../Workflows/Flows/Trading/OpenPosition.cs | 230 ++ .../Workflows/WorkflowService.cs | 126 + src/Managing.Bootstrap/ApiBootstrap.cs | 157 ++ .../Managing.Bootstrap.csproj | 28 + src/Managing.Bootstrap/WorkersBootstrap.cs | 117 + src/Managing.Common/Constants.cs | 39 + src/Managing.Common/Enums.cs | 343 +++ src/Managing.Common/Managing.Common.csproj | 13 + src/Managing.Core/AsyncLazy.cs | 22 + src/Managing.Core/DateHelpers.cs | 10 + src/Managing.Core/Managing.Core.csproj | 20 + src/Managing.Core/MathHelpers.cs | 25 + .../GlobalErrorHandlingMiddleware.cs | 63 + src/Managing.Core/MiscExtensions.cs | 66 + src/Managing.Core/ValueObject.cs | 40 + src/Managing.Docker/.dockerignore | 29 + .../docker-compose.sandbox.yml | 67 + src/Managing.Docker/docker-compose.yml | 46 + .../Managing.Domain.Workers.csproj | 9 + src/Managing.Domain.Workers/Worker.cs | 7 + src/Managing.Domain/Accounts/Account.cs | 19 + src/Managing.Domain/Accounts/Balance.cs | 14 + src/Managing.Domain/Backtests/Backtest.cs | 74 + src/Managing.Domain/Bots/Bot.cs | 75 + src/Managing.Domain/Bots/IBot.cs | 12 + src/Managing.Domain/Candles/Candle.cs | 34 + .../Candles/CandleExtensions.cs | 71 + src/Managing.Domain/Evm/Chain.cs | 8 + src/Managing.Domain/Evm/EvmBalance.cs | 12 + src/Managing.Domain/Evm/Holder.cs | 14 + src/Managing.Domain/Evm/HolderExtensions.cs | 31 + src/Managing.Domain/Evm/Nft.cs | 17 + src/Managing.Domain/Evm/Subgraph.cs | 9 + src/Managing.Domain/Managing.Domain.csproj | 19 + .../MoneyManagements/MoneyManagement.cs | 28 + src/Managing.Domain/Scenarios/Scenario.cs | 20 + .../Scenarios/ScenarioHelpers.cs | 148 ++ .../Shared/Helpers/RiskHelpers.cs | 52 + .../Shared/Helpers/TradingBox.cs | 161 ++ .../Shared/Helpers/TradingHelpers.cs | 99 + src/Managing.Domain/Shared/Rules/Check.cs | 13 + .../Shared/Rules/IValidationRules.cs | 9 + .../Shared/Rules/RuleException.cs | 9 + src/Managing.Domain/Statistics/Spotlight.cs | 38 + .../Statistics/TopVolumeTicker.cs | 12 + src/Managing.Domain/Statistics/Trader.cs | 12 + .../Strategies/Base/EmaBaseStrategy.cs | 39 + .../Strategies/ChandelierExitStrategy.cs | 115 + .../Strategies/EmaCrossStrategy.cs | 69 + .../Strategies/EmaTrendStrategy.cs | 65 + src/Managing.Domain/Strategies/IStrategy.cs | 20 + .../Strategies/MACDCrossStrategy.cs | 102 + .../RSIDivergenceConfirmStrategy.cs | 252 ++ .../Strategies/RSIDivergenceStrategy.cs | 234 ++ .../Rules/CloseHigherThanThePreviousHigh.cs | 24 + .../Rules/CloseLowerThanThePreviousHigh.cs | 24 + .../Strategies/Rules/RSIShouldBeBullish.cs | 21 + src/Managing.Domain/Strategies/STCStrategy.cs | 103 + src/Managing.Domain/Strategies/Signal.cs | 62 + .../Strategies/StDevContext.cs | 102 + .../Strategies/StochRsiTrendStrategy.cs | 112 + src/Managing.Domain/Strategies/Strategy.cs | 58 + .../Strategies/SuperTrendStrategy.cs | 104 + .../Strategies/ThreeWhiteSoldiersStrategy.cs | 55 + src/Managing.Domain/Trades/Fee.cs | 10 + src/Managing.Domain/Trades/OrderBookEntry.cs | 7 + .../Trades/OrderBookExtensions.cs | 35 + src/Managing.Domain/Trades/Orderbook.cs | 8 + src/Managing.Domain/Trades/Position.cs | 57 + src/Managing.Domain/Trades/ProfitAndLoss.cs | 102 + src/Managing.Domain/Trades/Trade.cs | 69 + src/Managing.Domain/Users/User.cs | 9 + src/Managing.Domain/Workers/Worker.cs | 13 + src/Managing.Domain/Workflows/FlowBase.cs | 19 + .../Workflows/FlowParameter.cs | 7 + src/Managing.Domain/Workflows/IFlow.cs | 26 + .../Workflows/Synthetics/SyntheticFlow.cs | 15 + .../Synthetics/SyntheticFlowParameter.cs | 11 + .../Workflows/Synthetics/SyntheticWorkflow.cs | 16 + src/Managing.Domain/Workflows/Workflow.cs | 24 + .../AccountRepository.cs | 46 + .../BacktestRepository.cs | 38 + .../CandleRepository.cs | 105 + .../Abstractions/IInfluxDbRepository.cs | 11 + .../Abstractions/IInfluxDbSettings.cs | 8 + .../InfluxDb/InfluxDbRepository.cs | 32 + .../InfluxDb/Models/InfluxDbSettings.cs | 10 + .../InfluxDb/Models/PriceDto.cs | 36 + .../InfluxDb/Models/TickerDto.cs | 1 + .../InfluxDb/PriceHelpers.cs | 58 + .../Managing.Infrastructure.Databases.csproj | 21 + .../MongoDb/Abstractions/IMongoRepository.cs | 57 + .../Attributes/BsonCollectionAttribute.cs | 13 + .../MongoDb/Collections/AccountDto.cs | 16 + .../MongoDb/Collections/BacktestDto.cs | 26 + .../MongoDb/Collections/BadTraderDto.cs | 17 + .../MongoDb/Collections/BestTraderDto.cs | 16 + .../MongoDb/Collections/CandleDto.cs | 28 + .../MongoDb/Collections/FeeDto.cs | 13 + .../MongoDb/Collections/MoneyManagementDto.cs | 18 + .../MongoDb/Collections/PositionDto.cs | 27 + .../MongoDb/Collections/ScenarioDto.cs | 12 + .../MongoDb/Collections/SignalDto.cs | 21 + .../Collections/SpotlighOverviewDto.cs | 30 + .../MongoDb/Collections/StrategyDto.cs | 23 + .../MongoDb/Collections/TopVolumeTickerDto.cs | 15 + .../MongoDb/Collections/TradeDto.cs | 24 + .../MongoDb/Collections/UserDto.cs | 10 + .../MongoDb/Collections/WorkerDto.cs | 16 + .../MongoDb/Collections/WorkflowDto.cs | 16 + .../MongoDb/Configurations/Document.cs | 13 + .../MongoDb/Configurations/IDocument.cs | 14 + .../IManagingDatabaseSettings.cs | 8 + .../ManagingDatabaseSettings.cs | 7 + .../MongoDb/MongoHelpers.cs | 20 + .../MongoDb/MongoMappers.cs | 654 +++++ .../MongoDb/MongoRepository.cs | 178 ++ .../Seeds/ManagingDb/MoneyManagement.bson.gz | Bin 0 -> 388 bytes .../MoneyManagement.metadata.json.gz | Bin 0 -> 133 bytes .../Seeds/ManagingDb/Scenarios.bson.gz | Bin 0 -> 329 bytes .../ManagingDb/Scenarios.metadata.json.gz | Bin 0 -> 157 bytes .../Seeds/ManagingDb/Strategies.bson.gz | Bin 0 -> 271 bytes .../ManagingDb/Strategies.metadata.json.gz | Bin 0 -> 133 bytes .../SettingsRepository.cs | 53 + .../StatisticRepository.cs | 103 + .../TradingRepository.cs | 150 ++ .../UserRepository.cs | 28 + .../WorkerRepository.cs | 60 + .../WorkflowRepository.cs | 48 + .../Abstractions/IExchangeProcessor.cs | 39 + .../CandleHelpers.cs | 82 + .../Configurations/ExchangeConfiguration.cs | 23 + .../ExchangeService.cs | 242 ++ .../ExchangeStream.cs | 41 + .../Exchanges/BaseProcessor.cs | 30 + .../Exchanges/BinanceProcessor.cs | 172 ++ .../Exchanges/EvmProcessor.cs | 172 ++ .../Exchanges/FtxProcessor.cs | 199 ++ .../Exchanges/KrakenProcessor.cs | 138 + .../Helpers/BinanceHelpers.cs | 195 ++ .../Helpers/FtxHelpers.cs | 286 ++ .../Helpers/KrakenHelpers.cs | 46 + .../Managing.Infrastructure.Exchanges.csproj | 23 + .../Discord/CommandHandler.cs | 142 + .../Discord/DiscordCommands.cs | 223 ++ .../Discord/DiscordHelpers.cs | 80 + .../Discord/DiscordService.cs | 557 ++++ .../Discord/DiscordSettings.cs | 37 + .../Discord/MessengerHelpers.cs | 15 + .../Discord/SlashCommands.cs | 28 + .../Managing.Infrastructure.Messengers.csproj | 19 + .../Attributes/BsonCollectionAttribute.cs | 13 + .../Collections/AccountDto.cs | 14 + .../Collections/BacktestDto.cs | 24 + .../Collections/CandleDto.cs | 28 + .../Collections/MoneyManagementDto.cs | 18 + .../Collections/PositionDto.cs | 24 + .../Collections/ScenarioDto.cs | 12 + .../Collections/SignalDto.cs | 20 + .../Collections/StrategyDto.cs | 21 + .../Collections/TopVolumeTickerDto.cs | 15 + .../Collections/TradeDto.cs | 24 + .../Collections/WorkerDto.cs | 15 + .../Configurations/Document.cs | 13 + .../Configurations/IDocument.cs | 14 + .../IManagingDatabaseSettings.cs | 8 + .../ManagingDatabaseSettings.cs | 7 + .../IMongoRepository.cs | 55 + .../Managing.Infrastructure.MongoDb.csproj | 18 + .../MongoHelpers.cs | 20 + .../MongoRepository.cs | 173 ++ .../Seeds/ManagingDb/MoneyManagement.bson.gz | Bin 0 -> 388 bytes .../MoneyManagement.metadata.json.gz | Bin 0 -> 133 bytes .../Seeds/ManagingDb/Scenarios.bson.gz | Bin 0 -> 329 bytes .../ManagingDb/Scenarios.metadata.json.gz | Bin 0 -> 157 bytes .../Seeds/ManagingDb/Strategies.bson.gz | Bin 0 -> 271 bytes .../ManagingDb/Strategies.metadata.json.gz | Bin 0 -> 133 bytes .../CacheService.cs | 74 + .../Managing.Infrastructure.Storage.csproj | 19 + .../TaskCache.cs | 82 + .../EvmManagerTests.cs | 379 +++ .../ExchangeServicesTests.cs | 200 ++ .../Managing.Infrastructure.Tests.csproj | 27 + .../SubgraphTests.cs | 56 + .../TradaoTests.cs | 24 + .../Abstractions/ISubgraphPrices.cs | 12 + .../Abstractions/IUniswap.cs | 9 + .../EvmManager.cs | 620 +++++ .../Extensions/PriceExtensions.cs | 75 + .../Managing.Infrastructure.Evm.csproj | 26 + .../Models/GeckoToken.cs | 7 + .../Models/Gmx/GmxOrder.cs | 19 + .../Models/Gmx/GmxOrderIndex.cs | 9 + .../Models/Gmx/GmxPosition.cs | 19 + .../Models/Gmx/GmxPrices.cs | 19 + .../Models/TradaoList.cs | 20 + .../Models/TradaoUserDetails.cs | 49 + .../Referentials/Arbitrum.cs | 57 + .../Services/ChainService.cs | 70 + .../Services/Gmx/GmxHelpers.cs | 139 + .../Services/Gmx/GmxMappers.cs | 177 ++ .../Services/Gmx/GmxService.cs | 458 ++++ .../Services/NftService.cs | 45 + .../Services/SubgraphService.cs | 45 + .../Services/TokenService.cs | 65 + .../Services/TradaoService.cs | 98 + .../Subgraphs/Chainlink.cs | 122 + .../Subgraphs/ChainlinkGmx.cs | 92 + .../Subgraphs/Gbc.cs | 104 + .../Subgraphs/Models/GbcPrices.cs | 15 + .../Subgraphs/Models/Pair.cs | 11 + .../Subgraphs/Models/Pools.cs | 6 + .../Subgraphs/Models/Price.cs | 17 + .../Subgraphs/Models/PricesChainlink.cs | 6 + .../Subgraphs/Models/Round.cs | 8 + .../Subgraphs/Models/Rounds.cs | 6 + .../Subgraphs/Models/TokenDetails.cs | 20 + .../Subgraphs/Models/TopTokens.cs | 6 + .../Subgraphs/SubgraphExtensions.cs | 41 + .../Subgraphs/Uniswap.cs | 85 + .../Managing.Infrastructure.Worker.csproj | 7 + .../WorkerService.cs | 8 + src/Managing.Tools.ABI/Gmx/core/OrderBook.abi | 1 + src/Managing.Tools.ABI/Gmx/core/OrderBook.bin | 1 + .../Gmx/core/OrderBookReader.abi | 1 + .../Gmx/core/OrderBookReader.bin | 1 + .../Gmx/core/PositionRouter.abi | 1 + .../Gmx/core/PositionRouter.bin | 1 + src/Managing.Tools.ABI/Gmx/core/Reader.abi | 1 + src/Managing.Tools.ABI/Gmx/core/Reader.bin | 1 + src/Managing.Tools.ABI/Gmx/core/Router.abi | 1 + src/Managing.Tools.ABI/Gmx/core/Router.bin | 1 + .../Managing.Tools.ABI.csproj | 14 + .../ContractDefinition/OrderBookDefinition.cs | 1171 +++++++++ .../OrderBook/OrderBookService.cs | 859 ++++++ .../OrderBookReaderDefinition.cs | 94 + .../OrderBookReader/OrderBookReaderService.cs | 88 + .../PositionRouterDefinition.cs | 1353 ++++++++++ .../PositionRouter/PositionRouterService.cs | 1142 ++++++++ src/Managing.Tools.ABI/Program.cs | 2 + .../ContractDefinition/ReaderDefinition.cs | 540 ++++ .../Reader/ReaderService.cs | 443 ++++ .../ContractDefinition/RouterDefinition.cs | 466 ++++ .../Router/RouterService.cs | 702 +++++ src/Managing.WebApp/.env | 6 + src/Managing.WebApp/.eslintignore | 2 + src/Managing.WebApp/.eslintrc | 98 + src/Managing.WebApp/.gitattributes | 3 + .../.github/workflows/build.yml | 18 + .../.github/workflows/lint.yml | 18 + .../.github/workflows/test.yml | 18 + .../.github/workflows/typecheck.yml | 18 + src/Managing.WebApp/.gitignore | 5 + src/Managing.WebApp/.prettierignore | 5 + src/Managing.WebApp/.prettierrc | 4 + src/Managing.WebApp/Dockerfile | 20 + src/Managing.WebApp/LICENSE | 21 + src/Managing.WebApp/README.md | 94 + src/Managing.WebApp/index.html | 16 + src/Managing.WebApp/jest.config.js | 35 + src/Managing.WebApp/mockServiceWorker.js | 338 +++ src/Managing.WebApp/package.json | 102 + src/Managing.WebApp/postcss.config.js | 6 + src/Managing.WebApp/prettier.config.js | 4 + src/Managing.WebApp/scripts/validate | 11 + src/Managing.WebApp/src/app/index.tsx | 13 + .../src/app/providers/Hubs.tsx | 18 + src/Managing.WebApp/src/app/routes/index.tsx | 118 + .../src/app/store/accountStore.tsx | 22 + .../src/app/store/apiStore.tsx | 28 + .../src/app/store/flowStore.tsx | 28 + .../app/store/selectors/workflowSelector.tsx | 13 + .../src/app/store/workflowStore.tsx | 113 + src/Managing.WebApp/src/assets/img/logo.png | Bin 0 -> 33041 bytes .../src/assets/img/tradingview.png | Bin 0 -> 95197 bytes .../src/components/atoms/List/List.tsx | 7 + .../src/components/atoms/Loader/Loader.tsx | 16 + .../src/components/atoms/MyLink/MyLink.tsx | 42 + .../src/components/atoms/Select/Select.tsx | 7 + .../src/components/atoms/Slider/Slider.tsx | 30 + .../src/components/atoms/index.tsx | 5 + .../src/components/mollecules/Card/Card.tsx | 45 + .../mollecules/CardText/CardText.tsx | 77 + .../mollecules/FormInput/FormInput.tsx | 24 + .../mollecules/GridTile/GridTile.tsx | 38 + .../src/components/mollecules/LogIn/LogIn.tsx | 101 + .../components/mollecules/LogIn/Profile.tsx | 26 + .../src/components/mollecules/Modal/Modal.tsx | 34 + .../mollecules/Modal/ModalHeader.tsx | 19 + .../components/mollecules/NavBar/NavBar.tsx | 130 + .../components/mollecules/NavItem/NavItem.tsx | 33 + .../mollecules/PieChart/PieChart.tsx | 46 + .../mollecules/Table/SelectColumnFilter.tsx | 36 + .../src/components/mollecules/Table/Table.tsx | 231 ++ .../src/components/mollecules/Tabs/Tabs.tsx | 77 + .../ThemeSelector/ThemeSelector.tsx | 21 + .../src/components/mollecules/Toast/Toast.tsx | 31 + .../src/components/mollecules/index.tsx | 14 + .../components/organism/Account/Account.tsx | 13 + .../organism/ActiveBots/ActiveBots.tsx | 211 ++ .../organism/Backtest/backtestCards.tsx | 309 +++ .../organism/Backtest/backtestModal.tsx | 361 +++ .../organism/Backtest/backtestRowDetails.tsx | 47 + .../organism/Backtest/backtestTable.tsx | 275 ++ .../CustomMoneyManagement.tsx | 104 + .../organism/Positions/PositionList.tsx | 128 + .../Positions/PositionStatusBadge.tsx | 30 + .../SpotLightBadge/SpotLightBadge.tsx | 31 + .../organism/StatusBadge/StatusBadge.tsx | 14 + .../organism/Trading/CardPositionItem.tsx | 34 + .../components/organism/Trading/Summary.tsx | 133 + .../Trading/TradeChart/TradeChart.tsx | 302 +++ .../components/organism/Workflow/flowItem.tsx | 22 + .../organism/Workflow/flows/Flow.tsx | 40 + .../Workflow/flows/feed/feedTicker.tsx | 62 + .../flows/strategies/RsiDivergenceFlow.tsx | 58 + .../flows/trading/OpenPositionFlow.tsx | 96 + .../organism/Workflow/flows/trading/test.tsx | 24 + .../organism/Workflow/workflowCanvas.tsx | 255 ++ .../organism/Workflow/workflowForm.tsx | 69 + .../organism/Workflow/workflowSidebar.tsx | 35 + .../src/components/organism/index.tsx | 10 + src/Managing.WebApp/src/favicon.svg | 15 + .../src/generated/AuthorizedApiBase.ts | 30 + src/Managing.WebApp/src/generated/IConfig.ts | 7 + .../src/generated/ManagingApi.ts | 2310 +++++++++++++++++ .../src/generated/ManagingWorkerApi.tsx | 158 ++ src/Managing.WebApp/src/global/enum.tsx | 0 src/Managing.WebApp/src/global/helpers.tsx | 9 + src/Managing.WebApp/src/global/type.tsx | 289 +++ src/Managing.WebApp/src/hooks/useAccounts.tsx | 27 + src/Managing.WebApp/src/hooks/useCookie.ts | 40 + .../src/hooks/useCustomEffect.tsx | 6 + .../src/hooks/useDidUpdateEffect.ts | 21 + .../src/hooks/useLocalStorage.tsx | 39 + src/Managing.WebApp/src/hooks/useModal.tsx | 11 + .../src/hooks/useOutsideClick.tsx | 38 + .../src/hooks/usePositionHub.tsx | 47 + src/Managing.WebApp/src/hooks/useTheme.tsx | 149 ++ src/Managing.WebApp/src/hooks/useToggle.tsx | 9 + src/Managing.WebApp/src/layouts/index.tsx | 31 + src/Managing.WebApp/src/logo.svg | 7 + src/Managing.WebApp/src/main.tsx | 38 + .../src/pages/authPage/auth.tsx | 30 + .../src/pages/backtestPage/backtest.tsx | 40 + .../src/pages/backtestPage/backtestLoop.tsx | 36 + .../pages/backtestPage/backtestPlayground.tsx | 35 + .../pages/backtestPage/backtestScanner.tsx | 189 ++ .../src/pages/botsPage/botList.tsx | 265 ++ .../src/pages/botsPage/bots.tsx | 280 ++ .../dashboardPage/analytics/analytics.tsx | 52 + .../src/pages/dashboardPage/analytics/cme.tsx | 46 + .../pages/dashboardPage/analytics/futures.tsx | 46 + .../pages/dashboardPage/analytics/options.tsx | 37 + .../pages/dashboardPage/analytics/prices.tsx | 37 + .../pages/dashboardPage/analytics/spot.tsx | 28 + .../src/pages/dashboardPage/dashboard.tsx | 35 + .../src/pages/dashboardPage/monitoring.tsx | 16 + .../src/pages/desk/deskWidget.tsx | 21 + .../pages/desk/widgets/OpenPositionWidget.tsx | 163 ++ .../pages/desk/widgets/PositionsWidget.tsx | 32 + .../pages/desk/widgets/TradeChartWidget.tsx | 91 + .../src/pages/desk/widgets/WorkersWidget.tsx | 246 ++ .../src/pages/scenarioPage/scenario.tsx | 38 + .../src/pages/scenarioPage/scenarioList.tsx | 110 + .../src/pages/scenarioPage/scenarioTable.tsx | 83 + .../src/pages/scenarioPage/strategyList.tsx | 401 +++ .../src/pages/scenarioPage/strategyTable.tsx | 94 + .../settingsPage/account/accountModal.tsx | 178 ++ .../account/accountRowDetails.tsx | 63 + .../settingsPage/account/accountSettings.tsx | 47 + .../settingsPage/account/accountTable.tsx | 163 ++ .../moneymanagement/moneyManagement.tsx | 52 + .../moneymanagement/moneyManagementModal.tsx | 190 ++ .../moneymanagement/moneymanagementTable.tsx | 133 + .../src/pages/settingsPage/settings.tsx | 46 + .../src/pages/settingsPage/theme.tsx | 14 + .../src/pages/toolsPage/rektFees.tsx | 158 ++ .../pages/toolsPage/spotlight/spotlight.tsx | 76 + .../toolsPage/spotlight/spotlightSummary.tsx | 210 ++ .../toolsPage/spotlight/spotlightTable.tsx | 94 + .../src/pages/toolsPage/tools.tsx | 36 + .../src/pages/web3Page/web3.tsx | 104 + .../src/pages/workflow/workflows.tsx | 98 + src/Managing.WebApp/src/polyfills.ts | 4 + .../src/smartcontracts/courses/mood.sol | 16 + .../src/smartcontracts/courses/odaNFT.sol | 14 + .../src/smartcontracts/courses/odaToken.sol | 10 + src/Managing.WebApp/src/stores/store.tsx | 3 + src/Managing.WebApp/src/styles/app.css | 46 + src/Managing.WebApp/src/styles/globals.css | 23 + src/Managing.WebApp/src/vite-env.d.ts | 3 + src/Managing.WebApp/tailwind.config.js | 12 + src/Managing.WebApp/tsconfig.json | 21 + src/Managing.WebApp/vite.config.ts | 20 + src/Managing.sln | 245 ++ src/appsettings.Lowpro.json | 39 + src/appsettings.Oda-Sandbox.json | 37 + src/captain-definition | 4 + src/docker-compose.dcproj | 25 + src/docker-compose.override.yml | 29 + src/docker-compose.yml | 24 + src/launchSettings.json | 28 + 587 files changed, 44288 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/caprover.yml create mode 100644 .gitignore create mode 100644 Documentation.md create mode 100644 README.md create mode 100644 assets/NSwagConfig.nswag create mode 100644 assets/Todo-v1.txt create mode 100644 assets/Todo-v2.md create mode 100644 assets/documentation/Architecture.drawio create mode 100644 assets/documentation/EndGame.md create mode 100644 assets/documentation/Flows.md create mode 100644 assets/documentation/PositionWorkflow.md create mode 100644 assets/img/Architecture.png create mode 100644 assets/img/doc-docker.png create mode 100644 assets/img/doc-influxdb-apikeys-1.png create mode 100644 assets/img/doc-influxdb-apikeys-2.png create mode 100644 assets/img/doc-influxdb.png create mode 100644 assets/img/doc-settings.png create mode 100644 captain-definition create mode 100644 scripts/build_and_run.sh create mode 100644 scripts/clean-front-end-code.cmd create mode 100644 scripts/docker-deploy-sandbox - Copy.cmd create mode 100644 scripts/docker-deploy-sandbox.cmd create mode 100644 scripts/docker-redeploy-oda.cmd create mode 100644 src/.dockerignore create mode 100644 src/.editorconfig create mode 100644 src/Managing.Api.Workers.csproj create mode 100644 src/Managing.Api.Workers/Controllers/WorkerController.cs create mode 100644 src/Managing.Api.Workers/Dockerfile create mode 100644 src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs create mode 100644 src/Managing.Api.Workers/Managing.Api.Workers.csproj create mode 100644 src/Managing.Api.Workers/Program.cs create mode 100644 src/Managing.Api.Workers/Workers/BaseWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/FeeWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/LeaderboardWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PositionFetcher.cs create mode 100644 src/Managing.Api.Workers/Workers/PositionManagerWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesBaseWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesFifteenMinutesWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/SpotlightWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs create mode 100644 src/Managing.Api.Workers/Workers/TraderWatcher.cs create mode 100644 src/Managing.Api.Workers/appsettings.Development.json create mode 100644 src/Managing.Api.Workers/appsettings.Khalid.json create mode 100644 src/Managing.Api.Workers/appsettings.Lowpro.json create mode 100644 src/Managing.Api.Workers/appsettings.Oda-docker.json create mode 100644 src/Managing.Api.Workers/appsettings.Oda.json create mode 100644 src/Managing.Api/Authorization/JwtMiddleware.cs create mode 100644 src/Managing.Api/Authorization/JwtUtils.cs create mode 100644 src/Managing.Api/Controllers/AccountController.cs create mode 100644 src/Managing.Api/Controllers/BacktestController.cs create mode 100644 src/Managing.Api/Controllers/BaseController.cs create mode 100644 src/Managing.Api/Controllers/BotController.cs create mode 100644 src/Managing.Api/Controllers/DataController.cs create mode 100644 src/Managing.Api/Controllers/MoneyManagementController.cs create mode 100644 src/Managing.Api/Controllers/ScenarioController.cs create mode 100644 src/Managing.Api/Controllers/SettingsController.cs create mode 100644 src/Managing.Api/Controllers/TradingController.cs create mode 100644 src/Managing.Api/Controllers/UserController.cs create mode 100644 src/Managing.Api/Controllers/WorkflowController.cs create mode 100644 src/Managing.Api/Dockerfile create mode 100644 src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs create mode 100644 src/Managing.Api/Filters/EnumSchemaFilter.cs create mode 100644 src/Managing.Api/Managing.Api.csproj create mode 100644 src/Managing.Api/Models/Requests/CreateScenarioRequest.cs create mode 100644 src/Managing.Api/Models/Requests/CreateStrategyRequest.cs create mode 100644 src/Managing.Api/Models/Requests/LoginRequest.cs create mode 100644 src/Managing.Api/Models/Requests/RunBacktestRequest.cs create mode 100644 src/Managing.Api/Models/Requests/StartBotRequest.cs create mode 100644 src/Managing.Api/Models/Responses/TradingBot.cs create mode 100644 src/Managing.Api/Program.cs create mode 100644 src/Managing.Api/appsettings.Development.json create mode 100644 src/Managing.Api/appsettings.Khalid.json create mode 100644 src/Managing.Api/appsettings.Lowpro.json create mode 100644 src/Managing.Api/appsettings.Oda-Sandbox.json create mode 100644 src/Managing.Api/appsettings.Oda-docker.json create mode 100644 src/Managing.Api/appsettings.Oda.json create mode 100644 src/Managing.Api/appsettings.json create mode 100644 src/Managing.Api/captain-definition create mode 100644 src/Managing.Api/failures.txt create mode 100644 src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj create mode 100644 src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/ICandleRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IEvmManager.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IUserRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs create mode 100644 src/Managing.Application.Abstractions/Repositories/IWorkflowRepository.cs create mode 100644 src/Managing.Application.Abstractions/Services/IAccountService.cs create mode 100644 src/Managing.Application.Abstractions/Services/IBacktester.cs create mode 100644 src/Managing.Application.Abstractions/Services/IDiscordService.cs create mode 100644 src/Managing.Application.Abstractions/Services/IExchangeService.cs create mode 100644 src/Managing.Application.Abstractions/Services/IExchangeStream.cs create mode 100644 src/Managing.Application.Abstractions/Services/IMessengerService.cs create mode 100644 src/Managing.Application.Abstractions/Services/IStreamService.cs create mode 100644 src/Managing.Application.Abstractions/Services/ITickerService.cs create mode 100644 src/Managing.Application.Abstractions/Services/ITradaoService.cs create mode 100644 src/Managing.Application.Abstractions/Services/ITradingService.cs create mode 100644 src/Managing.Application.Abstractions/Services/IUserService.cs create mode 100644 src/Managing.Application.Tests/BaseTests.cs create mode 100644 src/Managing.Application.Tests/BotsTests.cs create mode 100644 src/Managing.Application.Tests/CandleHelpersTests.cs create mode 100644 src/Managing.Application.Tests/Managing.Application.Tests.csproj create mode 100644 src/Managing.Application.Tests/MathHelpersTests.cs create mode 100644 src/Managing.Application.Tests/PositionTests.cs create mode 100644 src/Managing.Application.Tests/ProfitAndLossTests.cs create mode 100644 src/Managing.Application.Tests/RiskHelpersTests.cs create mode 100644 src/Managing.Application.Tests/StrategyTests.cs create mode 100644 src/Managing.Application.Tests/TradingBaseTests.cs create mode 100644 src/Managing.Application.Tests/WorkflowTests.cs create mode 100644 src/Managing.Application.Workers/Abstractions/IPricesService.cs create mode 100644 src/Managing.Application.Workers/Abstractions/IStatisticService.cs create mode 100644 src/Managing.Application.Workers/Abstractions/IWorkerService.cs create mode 100644 src/Managing.Application.Workers/Managing.Application.Workers.csproj create mode 100644 src/Managing.Application.Workers/PricesService.cs create mode 100644 src/Managing.Application.Workers/StatisticService.cs create mode 100644 src/Managing.Application.Workers/WorkerService.cs create mode 100644 src/Managing.Application/Abstractions/IBotFactory.cs create mode 100644 src/Managing.Application/Abstractions/ICacheService.cs create mode 100644 src/Managing.Application/Abstractions/ICommandHandler.cs create mode 100644 src/Managing.Application/Abstractions/IFlowFactory.cs create mode 100644 src/Managing.Application/Abstractions/IMoneyManagementService.cs create mode 100644 src/Managing.Application/Abstractions/IScenarioService.cs create mode 100644 src/Managing.Application/Abstractions/ISettingsService.cs create mode 100644 src/Managing.Application/Abstractions/ITaskCache.cs create mode 100644 src/Managing.Application/Abstractions/ITradingBot.cs create mode 100644 src/Managing.Application/Abstractions/IWorkflowService.cs create mode 100644 src/Managing.Application/Accounts/AccountService.cs create mode 100644 src/Managing.Application/Backtesting/Backtester.cs create mode 100644 src/Managing.Application/Bots/Base/BotFactory.cs create mode 100644 src/Managing.Application/Bots/FlippingBot.cs create mode 100644 src/Managing.Application/Bots/ScalpingBot.cs create mode 100644 src/Managing.Application/Bots/SimpleBot.cs create mode 100644 src/Managing.Application/Bots/TradingBot.cs create mode 100644 src/Managing.Application/Hubs/BacktestHub.cs create mode 100644 src/Managing.Application/Hubs/BotHub.cs create mode 100644 src/Managing.Application/Hubs/CandleHub.cs create mode 100644 src/Managing.Application/Hubs/PositionHub.cs create mode 100644 src/Managing.Application/ManageBot/Commands/DeleteBotCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/GetActiveBotsCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/StartBotCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/StopBotCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs create mode 100644 src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/RestartBotCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/StartBotCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/StopBotCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs create mode 100644 src/Managing.Application/Managing.Application.csproj create mode 100644 src/Managing.Application/MoneyManagements/MoneyManagementService.cs create mode 100644 src/Managing.Application/Scenarios/ScenarioService.cs create mode 100644 src/Managing.Application/Shared/Behaviours/IUnhandledExceptionBehaviour.cs create mode 100644 src/Managing.Application/Shared/Behaviours/UnhandledExceptionBehaviour.cs create mode 100644 src/Managing.Application/Shared/Behaviours/ValidationBehaviour.cs create mode 100644 src/Managing.Application/Shared/MessengerService.cs create mode 100644 src/Managing.Application/Shared/SettingsService.cs create mode 100644 src/Managing.Application/Shared/StreamService.cs create mode 100644 src/Managing.Application/Shared/TickerService.cs create mode 100644 src/Managing.Application/Trading/ClosePositionCommandHandler.cs create mode 100644 src/Managing.Application/Trading/Commands/ClosePositionCommand.cs create mode 100644 src/Managing.Application/Trading/Commands/GetPositionsCommand.cs create mode 100644 src/Managing.Application/Trading/Commands/GetTradeCommand.cs create mode 100644 src/Managing.Application/Trading/Commands/GetTradesCommand.cs create mode 100644 src/Managing.Application/Trading/Commands/OpenPositionRequest.cs create mode 100644 src/Managing.Application/Trading/GetPositionsCommandHandler.cs create mode 100644 src/Managing.Application/Trading/GetTradeCommandHandler.cs create mode 100644 src/Managing.Application/Trading/GetTradesCommandHandler.cs create mode 100644 src/Managing.Application/Trading/OpenPositionCommandHandler.cs create mode 100644 src/Managing.Application/Trading/TradingPolicies.cs create mode 100644 src/Managing.Application/Trading/TradingService.cs create mode 100644 src/Managing.Application/Users/UserService.cs create mode 100644 src/Managing.Application/Workflows/FlowFactory.cs create mode 100644 src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs create mode 100644 src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs create mode 100644 src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs create mode 100644 src/Managing.Application/Workflows/WorkflowService.cs create mode 100644 src/Managing.Bootstrap/ApiBootstrap.cs create mode 100644 src/Managing.Bootstrap/Managing.Bootstrap.csproj create mode 100644 src/Managing.Bootstrap/WorkersBootstrap.cs create mode 100644 src/Managing.Common/Constants.cs create mode 100644 src/Managing.Common/Enums.cs create mode 100644 src/Managing.Common/Managing.Common.csproj create mode 100644 src/Managing.Core/AsyncLazy.cs create mode 100644 src/Managing.Core/DateHelpers.cs create mode 100644 src/Managing.Core/Managing.Core.csproj create mode 100644 src/Managing.Core/MathHelpers.cs create mode 100644 src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs create mode 100644 src/Managing.Core/MiscExtensions.cs create mode 100644 src/Managing.Core/ValueObject.cs create mode 100644 src/Managing.Docker/.dockerignore create mode 100644 src/Managing.Docker/docker-compose.sandbox.yml create mode 100644 src/Managing.Docker/docker-compose.yml create mode 100644 src/Managing.Domain.Workers/Managing.Domain.Workers.csproj create mode 100644 src/Managing.Domain.Workers/Worker.cs create mode 100644 src/Managing.Domain/Accounts/Account.cs create mode 100644 src/Managing.Domain/Accounts/Balance.cs create mode 100644 src/Managing.Domain/Backtests/Backtest.cs create mode 100644 src/Managing.Domain/Bots/Bot.cs create mode 100644 src/Managing.Domain/Bots/IBot.cs create mode 100644 src/Managing.Domain/Candles/Candle.cs create mode 100644 src/Managing.Domain/Candles/CandleExtensions.cs create mode 100644 src/Managing.Domain/Evm/Chain.cs create mode 100644 src/Managing.Domain/Evm/EvmBalance.cs create mode 100644 src/Managing.Domain/Evm/Holder.cs create mode 100644 src/Managing.Domain/Evm/HolderExtensions.cs create mode 100644 src/Managing.Domain/Evm/Nft.cs create mode 100644 src/Managing.Domain/Evm/Subgraph.cs create mode 100644 src/Managing.Domain/Managing.Domain.csproj create mode 100644 src/Managing.Domain/MoneyManagements/MoneyManagement.cs create mode 100644 src/Managing.Domain/Scenarios/Scenario.cs create mode 100644 src/Managing.Domain/Scenarios/ScenarioHelpers.cs create mode 100644 src/Managing.Domain/Shared/Helpers/RiskHelpers.cs create mode 100644 src/Managing.Domain/Shared/Helpers/TradingBox.cs create mode 100644 src/Managing.Domain/Shared/Helpers/TradingHelpers.cs create mode 100644 src/Managing.Domain/Shared/Rules/Check.cs create mode 100644 src/Managing.Domain/Shared/Rules/IValidationRules.cs create mode 100644 src/Managing.Domain/Shared/Rules/RuleException.cs create mode 100644 src/Managing.Domain/Statistics/Spotlight.cs create mode 100644 src/Managing.Domain/Statistics/TopVolumeTicker.cs create mode 100644 src/Managing.Domain/Statistics/Trader.cs create mode 100644 src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs create mode 100644 src/Managing.Domain/Strategies/ChandelierExitStrategy.cs create mode 100644 src/Managing.Domain/Strategies/EmaCrossStrategy.cs create mode 100644 src/Managing.Domain/Strategies/EmaTrendStrategy.cs create mode 100644 src/Managing.Domain/Strategies/IStrategy.cs create mode 100644 src/Managing.Domain/Strategies/MACDCrossStrategy.cs create mode 100644 src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs create mode 100644 src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs create mode 100644 src/Managing.Domain/Strategies/Rules/CloseHigherThanThePreviousHigh.cs create mode 100644 src/Managing.Domain/Strategies/Rules/CloseLowerThanThePreviousHigh.cs create mode 100644 src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs create mode 100644 src/Managing.Domain/Strategies/STCStrategy.cs create mode 100644 src/Managing.Domain/Strategies/Signal.cs create mode 100644 src/Managing.Domain/Strategies/StDevContext.cs create mode 100644 src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs create mode 100644 src/Managing.Domain/Strategies/Strategy.cs create mode 100644 src/Managing.Domain/Strategies/SuperTrendStrategy.cs create mode 100644 src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs create mode 100644 src/Managing.Domain/Trades/Fee.cs create mode 100644 src/Managing.Domain/Trades/OrderBookEntry.cs create mode 100644 src/Managing.Domain/Trades/OrderBookExtensions.cs create mode 100644 src/Managing.Domain/Trades/Orderbook.cs create mode 100644 src/Managing.Domain/Trades/Position.cs create mode 100644 src/Managing.Domain/Trades/ProfitAndLoss.cs create mode 100644 src/Managing.Domain/Trades/Trade.cs create mode 100644 src/Managing.Domain/Users/User.cs create mode 100644 src/Managing.Domain/Workers/Worker.cs create mode 100644 src/Managing.Domain/Workflows/FlowBase.cs create mode 100644 src/Managing.Domain/Workflows/FlowParameter.cs create mode 100644 src/Managing.Domain/Workflows/IFlow.cs create mode 100644 src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs create mode 100644 src/Managing.Domain/Workflows/Synthetics/SyntheticFlowParameter.cs create mode 100644 src/Managing.Domain/Workflows/Synthetics/SyntheticWorkflow.cs create mode 100644 src/Managing.Domain/Workflows/Workflow.cs create mode 100644 src/Managing.Infrastructure.Database/AccountRepository.cs create mode 100644 src/Managing.Infrastructure.Database/BacktestRepository.cs create mode 100644 src/Managing.Infrastructure.Database/CandleRepository.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbRepository.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbSettings.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/InfluxDbRepository.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/Models/InfluxDbSettings.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/Models/PriceDto.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/Models/TickerDto.cs create mode 100644 src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs create mode 100644 src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/StrategyDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.metadata.json.gz create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.bson.gz create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Scenarios.metadata.json.gz create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.bson.gz create mode 100644 src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz create mode 100644 src/Managing.Infrastructure.Database/SettingsRepository.cs create mode 100644 src/Managing.Infrastructure.Database/StatisticRepository.cs create mode 100644 src/Managing.Infrastructure.Database/TradingRepository.cs create mode 100644 src/Managing.Infrastructure.Database/UserRepository.cs create mode 100644 src/Managing.Infrastructure.Database/WorkerRepository.cs create mode 100644 src/Managing.Infrastructure.Database/WorkflowRepository.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/CandleHelpers.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Configurations/ExchangeConfiguration.cs create mode 100644 src/Managing.Infrastructure.Exchanges/ExchangeService.cs create mode 100644 src/Managing.Infrastructure.Exchanges/ExchangeStream.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Helpers/KrakenHelpers.cs create mode 100644 src/Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj create mode 100644 src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/DiscordHelpers.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/DiscordSettings.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/MessengerHelpers.cs create mode 100644 src/Managing.Infrastructure.Messengers/Discord/SlashCommands.cs create mode 100644 src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj create mode 100644 src/Managing.Infrastructure.MongoDb/Attributes/BsonCollectionAttribute.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/AccountDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/BacktestDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/CandleDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/MoneyManagementDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/PositionDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/ScenarioDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/SignalDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/StrategyDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/TopVolumeTickerDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/TradeDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Collections/WorkerDto.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Configurations/Document.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Configurations/IDocument.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Configurations/IManagingDatabaseSettings.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Configurations/ManagingDatabaseSettings.cs create mode 100644 src/Managing.Infrastructure.MongoDb/IMongoRepository.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj create mode 100644 src/Managing.Infrastructure.MongoDb/MongoHelpers.cs create mode 100644 src/Managing.Infrastructure.MongoDb/MongoRepository.cs create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/MoneyManagement.metadata.json.gz create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Scenarios.bson.gz create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Scenarios.metadata.json.gz create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Strategies.bson.gz create mode 100644 src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz create mode 100644 src/Managing.Infrastructure.Storage/CacheService.cs create mode 100644 src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj create mode 100644 src/Managing.Infrastructure.Storage/TaskCache.cs create mode 100644 src/Managing.Infrastructure.Tests/EvmManagerTests.cs create mode 100644 src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs create mode 100644 src/Managing.Infrastructure.Tests/Managing.Infrastructure.Tests.csproj create mode 100644 src/Managing.Infrastructure.Tests/SubgraphTests.cs create mode 100644 src/Managing.Infrastructure.Tests/TradaoTests.cs create mode 100644 src/Managing.Infrastructure.Web3/Abstractions/ISubgraphPrices.cs create mode 100644 src/Managing.Infrastructure.Web3/Abstractions/IUniswap.cs create mode 100644 src/Managing.Infrastructure.Web3/EvmManager.cs create mode 100644 src/Managing.Infrastructure.Web3/Extensions/PriceExtensions.cs create mode 100644 src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj create mode 100644 src/Managing.Infrastructure.Web3/Models/GeckoToken.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrderIndex.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/TradaoList.cs create mode 100644 src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs create mode 100644 src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/ChainService.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/NftService.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/SubgraphService.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/TokenService.cs create mode 100644 src/Managing.Infrastructure.Web3/Services/TradaoService.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/GbcPrices.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/Pools.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/PricesChainlink.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/Round.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/Rounds.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/TokenDetails.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Models/TopTokens.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/SubgraphExtensions.cs create mode 100644 src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs create mode 100644 src/Managing.Infrastructure.Worker/Managing.Infrastructure.Worker.csproj create mode 100644 src/Managing.Infrastructure.Worker/WorkerService.cs create mode 100644 src/Managing.Tools.ABI/Gmx/core/OrderBook.abi create mode 100644 src/Managing.Tools.ABI/Gmx/core/OrderBook.bin create mode 100644 src/Managing.Tools.ABI/Gmx/core/OrderBookReader.abi create mode 100644 src/Managing.Tools.ABI/Gmx/core/OrderBookReader.bin create mode 100644 src/Managing.Tools.ABI/Gmx/core/PositionRouter.abi create mode 100644 src/Managing.Tools.ABI/Gmx/core/PositionRouter.bin create mode 100644 src/Managing.Tools.ABI/Gmx/core/Reader.abi create mode 100644 src/Managing.Tools.ABI/Gmx/core/Reader.bin create mode 100644 src/Managing.Tools.ABI/Gmx/core/Router.abi create mode 100644 src/Managing.Tools.ABI/Gmx/core/Router.bin create mode 100644 src/Managing.Tools.ABI/Managing.Tools.ABI.csproj create mode 100644 src/Managing.Tools.ABI/OrderBook/ContractDefinition/OrderBookDefinition.cs create mode 100644 src/Managing.Tools.ABI/OrderBook/OrderBookService.cs create mode 100644 src/Managing.Tools.ABI/OrderBookReader/ContractDefinition/OrderBookReaderDefinition.cs create mode 100644 src/Managing.Tools.ABI/OrderBookReader/OrderBookReaderService.cs create mode 100644 src/Managing.Tools.ABI/PositionRouter/ContractDefinition/PositionRouterDefinition.cs create mode 100644 src/Managing.Tools.ABI/PositionRouter/PositionRouterService.cs create mode 100644 src/Managing.Tools.ABI/Program.cs create mode 100644 src/Managing.Tools.ABI/Reader/ContractDefinition/ReaderDefinition.cs create mode 100644 src/Managing.Tools.ABI/Reader/ReaderService.cs create mode 100644 src/Managing.Tools.ABI/Router/ContractDefinition/RouterDefinition.cs create mode 100644 src/Managing.Tools.ABI/Router/RouterService.cs create mode 100644 src/Managing.WebApp/.env create mode 100644 src/Managing.WebApp/.eslintignore create mode 100644 src/Managing.WebApp/.eslintrc create mode 100644 src/Managing.WebApp/.gitattributes create mode 100644 src/Managing.WebApp/.github/workflows/build.yml create mode 100644 src/Managing.WebApp/.github/workflows/lint.yml create mode 100644 src/Managing.WebApp/.github/workflows/test.yml create mode 100644 src/Managing.WebApp/.github/workflows/typecheck.yml create mode 100644 src/Managing.WebApp/.gitignore create mode 100644 src/Managing.WebApp/.prettierignore create mode 100644 src/Managing.WebApp/.prettierrc create mode 100644 src/Managing.WebApp/Dockerfile create mode 100644 src/Managing.WebApp/LICENSE create mode 100644 src/Managing.WebApp/README.md create mode 100644 src/Managing.WebApp/index.html create mode 100644 src/Managing.WebApp/jest.config.js create mode 100644 src/Managing.WebApp/mockServiceWorker.js create mode 100644 src/Managing.WebApp/package.json create mode 100644 src/Managing.WebApp/postcss.config.js create mode 100644 src/Managing.WebApp/prettier.config.js create mode 100644 src/Managing.WebApp/scripts/validate create mode 100644 src/Managing.WebApp/src/app/index.tsx create mode 100644 src/Managing.WebApp/src/app/providers/Hubs.tsx create mode 100644 src/Managing.WebApp/src/app/routes/index.tsx create mode 100644 src/Managing.WebApp/src/app/store/accountStore.tsx create mode 100644 src/Managing.WebApp/src/app/store/apiStore.tsx create mode 100644 src/Managing.WebApp/src/app/store/flowStore.tsx create mode 100644 src/Managing.WebApp/src/app/store/selectors/workflowSelector.tsx create mode 100644 src/Managing.WebApp/src/app/store/workflowStore.tsx create mode 100644 src/Managing.WebApp/src/assets/img/logo.png create mode 100644 src/Managing.WebApp/src/assets/img/tradingview.png create mode 100644 src/Managing.WebApp/src/components/atoms/List/List.tsx create mode 100644 src/Managing.WebApp/src/components/atoms/Loader/Loader.tsx create mode 100644 src/Managing.WebApp/src/components/atoms/MyLink/MyLink.tsx create mode 100644 src/Managing.WebApp/src/components/atoms/Select/Select.tsx create mode 100644 src/Managing.WebApp/src/components/atoms/Slider/Slider.tsx create mode 100644 src/Managing.WebApp/src/components/atoms/index.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Card/Card.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/CardText/CardText.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/FormInput/FormInput.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/GridTile/GridTile.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/LogIn/Profile.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Modal/ModalHeader.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/NavItem/NavItem.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/PieChart/PieChart.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Table/SelectColumnFilter.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Table/Table.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/ThemeSelector/ThemeSelector.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/Toast/Toast.tsx create mode 100644 src/Managing.WebApp/src/components/mollecules/index.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Account/Account.tsx create mode 100644 src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx create mode 100644 src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Positions/PositionList.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Positions/PositionStatusBadge.tsx create mode 100644 src/Managing.WebApp/src/components/organism/SpotLightBadge/SpotLightBadge.tsx create mode 100644 src/Managing.WebApp/src/components/organism/StatusBadge/StatusBadge.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Trading/CardPositionItem.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Trading/Summary.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flowItem.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flows/Flow.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flows/feed/feedTicker.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flows/strategies/RsiDivergenceFlow.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flows/trading/OpenPositionFlow.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/flows/trading/test.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/workflowCanvas.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/workflowForm.tsx create mode 100644 src/Managing.WebApp/src/components/organism/Workflow/workflowSidebar.tsx create mode 100644 src/Managing.WebApp/src/components/organism/index.tsx create mode 100644 src/Managing.WebApp/src/favicon.svg create mode 100644 src/Managing.WebApp/src/generated/AuthorizedApiBase.ts create mode 100644 src/Managing.WebApp/src/generated/IConfig.ts create mode 100644 src/Managing.WebApp/src/generated/ManagingApi.ts create mode 100644 src/Managing.WebApp/src/generated/ManagingWorkerApi.tsx create mode 100644 src/Managing.WebApp/src/global/enum.tsx create mode 100644 src/Managing.WebApp/src/global/helpers.tsx create mode 100644 src/Managing.WebApp/src/global/type.tsx create mode 100644 src/Managing.WebApp/src/hooks/useAccounts.tsx create mode 100644 src/Managing.WebApp/src/hooks/useCookie.ts create mode 100644 src/Managing.WebApp/src/hooks/useCustomEffect.tsx create mode 100644 src/Managing.WebApp/src/hooks/useDidUpdateEffect.ts create mode 100644 src/Managing.WebApp/src/hooks/useLocalStorage.tsx create mode 100644 src/Managing.WebApp/src/hooks/useModal.tsx create mode 100644 src/Managing.WebApp/src/hooks/useOutsideClick.tsx create mode 100644 src/Managing.WebApp/src/hooks/usePositionHub.tsx create mode 100644 src/Managing.WebApp/src/hooks/useTheme.tsx create mode 100644 src/Managing.WebApp/src/hooks/useToggle.tsx create mode 100644 src/Managing.WebApp/src/layouts/index.tsx create mode 100644 src/Managing.WebApp/src/logo.svg create mode 100644 src/Managing.WebApp/src/main.tsx create mode 100644 src/Managing.WebApp/src/pages/authPage/auth.tsx create mode 100644 src/Managing.WebApp/src/pages/backtestPage/backtest.tsx create mode 100644 src/Managing.WebApp/src/pages/backtestPage/backtestLoop.tsx create mode 100644 src/Managing.WebApp/src/pages/backtestPage/backtestPlayground.tsx create mode 100644 src/Managing.WebApp/src/pages/backtestPage/backtestScanner.tsx create mode 100644 src/Managing.WebApp/src/pages/botsPage/botList.tsx create mode 100644 src/Managing.WebApp/src/pages/botsPage/bots.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/analytics.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/cme.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/futures.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/options.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/prices.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/analytics/spot.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/dashboard.tsx create mode 100644 src/Managing.WebApp/src/pages/dashboardPage/monitoring.tsx create mode 100644 src/Managing.WebApp/src/pages/desk/deskWidget.tsx create mode 100644 src/Managing.WebApp/src/pages/desk/widgets/OpenPositionWidget.tsx create mode 100644 src/Managing.WebApp/src/pages/desk/widgets/PositionsWidget.tsx create mode 100644 src/Managing.WebApp/src/pages/desk/widgets/TradeChartWidget.tsx create mode 100644 src/Managing.WebApp/src/pages/desk/widgets/WorkersWidget.tsx create mode 100644 src/Managing.WebApp/src/pages/scenarioPage/scenario.tsx create mode 100644 src/Managing.WebApp/src/pages/scenarioPage/scenarioList.tsx create mode 100644 src/Managing.WebApp/src/pages/scenarioPage/scenarioTable.tsx create mode 100644 src/Managing.WebApp/src/pages/scenarioPage/strategyList.tsx create mode 100644 src/Managing.WebApp/src/pages/scenarioPage/strategyTable.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/account/accountModal.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/account/accountRowDetails.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/account/accountSettings.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/account/accountTable.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagement.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagementModal.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/settings.tsx create mode 100644 src/Managing.WebApp/src/pages/settingsPage/theme.tsx create mode 100644 src/Managing.WebApp/src/pages/toolsPage/rektFees.tsx create mode 100644 src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlight.tsx create mode 100644 src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightSummary.tsx create mode 100644 src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightTable.tsx create mode 100644 src/Managing.WebApp/src/pages/toolsPage/tools.tsx create mode 100644 src/Managing.WebApp/src/pages/web3Page/web3.tsx create mode 100644 src/Managing.WebApp/src/pages/workflow/workflows.tsx create mode 100644 src/Managing.WebApp/src/polyfills.ts create mode 100644 src/Managing.WebApp/src/smartcontracts/courses/mood.sol create mode 100644 src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol create mode 100644 src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol create mode 100644 src/Managing.WebApp/src/stores/store.tsx create mode 100644 src/Managing.WebApp/src/styles/app.css create mode 100644 src/Managing.WebApp/src/styles/globals.css create mode 100644 src/Managing.WebApp/src/vite-env.d.ts create mode 100644 src/Managing.WebApp/tailwind.config.js create mode 100644 src/Managing.WebApp/tsconfig.json create mode 100644 src/Managing.WebApp/vite.config.ts create mode 100644 src/Managing.sln create mode 100644 src/appsettings.Lowpro.json create mode 100644 src/appsettings.Oda-Sandbox.json create mode 100644 src/captain-definition create mode 100644 src/docker-compose.dcproj create mode 100644 src/docker-compose.override.yml create mode 100644 src/docker-compose.yml create mode 100644 src/launchSettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0f644e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +LICENSE +README.md diff --git a/.github/workflows/caprover.yml b/.github/workflows/caprover.yml new file mode 100644 index 0000000..6cf9e0d --- /dev/null +++ b/.github/workflows/caprover.yml @@ -0,0 +1,35 @@ +name: Build & Deploy +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Build and test your app (customize as needed) + run: | + # Add your build and test commands here + # For example: + # npm install + # npm run build + # npm run test + - name: Create deploy.tar + uses: a7ul/tar-action@v1.1.0 + with: + command: c + cwd: "./" + files: | + scripts/build_and_run.sh + + captain-definition + outPath: deploy.tar + - name: Deploy App to CapRover + uses: caprover/deploy-from-github@v1.0.1 + with: + server: '${{ secrets.CAPROVER_SERVER }}' + app: '${{ secrets.APP_NAME }}' + token: '${{ secrets.MANAGING_APPS }}' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73e451a --- /dev/null +++ b/.gitignore @@ -0,0 +1,379 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +bin +obj + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ +/Managing/Managing.Infrastructure.Storage/bin/ +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Application.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Application.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Common.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Common.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Core.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Core.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Domain.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Domain.pdb +/Managing/Managing.Infrastructure.Storage/obj/Debug/netstandard2.0/Managing.Infrastructure.Storage.assets.cache +/Managing/Managing.Infrastructure.Storage/obj/Debug/netstandard2.0/Managing.Infrastructure.Storage.csprojAssemblyReference.cache +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.dgspec.json +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.g.props +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.g.targets +/Managing/Managing.Infrastructure.Storage/obj/project.assets.json +/Managing/Managing.Infrastructure.Storage/obj/project.nuget.cache +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Application.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Application.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Common.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Common.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Core.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Core.pdb +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Domain.dll +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Domain.pdb +/Managing/Managing.Infrastructure.Storage/obj/Debug/netstandard2.0/Managing.Infrastructure.Storage.assets.cache +/Managing/Managing.Infrastructure.Storage/obj/Debug/netstandard2.0/Managing.Infrastructure.Storage.csprojAssemblyReference.cache +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.dgspec.json +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.g.props +/Managing/Managing.Infrastructure.Storage/obj/Managing.Infrastructure.Storage.csproj.nuget.g.targets +/Managing/Managing.Infrastructure.Storage/obj/project.assets.json +/Managing/Managing.Infrastructure.Storage/obj/project.nuget.cache +/Managing/Managing.Infrastructure.Storage/bin/Debug/netstandard2.0/Managing.Application.dll +/Managing/src/Managing.Api/failures.txt +/Managing/src/Managing.Api.Workers/failures.txt + +.vscode +.vs +.history +../config.json +src/config.json +src/Managing.WebApp/src/pages/desk/desk.tsx +/src/Managing.Infrastructure.Tests/EvmBaseTests.cs +src/Managing.Api.Workers/appsettings.json +src/config.json +src/Managing.Api.Workers/appsettings.Oda-Sandbox.json +/src/Managing.WebApp/package-lock.json diff --git a/Documentation.md b/Documentation.md new file mode 100644 index 0000000..c047df3 --- /dev/null +++ b/Documentation.md @@ -0,0 +1,61 @@ +# How to launch the App +## Requirements +- NET .Core framework +- MongoDb +- Node.JS +- Discord server with API keys +- Alchemy Keys + +# First setup + + +## Generate dev certificate +- ```dotnet dev-certs https -ep \.aspnet\https\aspnetapp.pfx -p password``` +- ```dotnet dev-certs https --trust``` + +## Setup docker +- Update ```apps\src\Managing.Api\appsettings.MYNAME.json``` with your configuration settings +- Update ```apps\src\Managing.Api.Workers\appsettings.MYNAME.json``` with your configuration settings +- Then execute from the root of the project : ```.\scripts\docker-deploy-sandbox.cmd``` +- At this point, only Managing.Api.Workers should be NOT running. It because we didn't create an account. + +## Setup an account +- ```cd src/Managing.WebApp``` +- ```npm run dev``` + +- Go to front-end app ```http://localhost:3000/``` +- If request end up with a 500 error, you should open the url of the container and validate the certificate. +- Connect your ETH account +- Setup a username for your account +- On Account page : Create an EVM account for trading (Not necessary if you don't want to trade) + +## Setup InfluxDb +- Go to http://localhost:8086/ +- Then initialize InfluxDb + +![InfluxDb setup](assets/img/doc-influxdb.png) + +- Go to api keys + +![InfluxDb Api](assets/img/doc-influxdb-apikeys-1.png) +- Create an apikey and update the ```apps\src\Managing.Api.Workers\appsettings.MYNAME.json``` file with the api key + +![InfluxDb Api](assets/img/doc-influxdb-apikeys-2.png) +- Then redeploy by executing : ```.\scripts\docker-deploy-sandbox.cmd``` +- Check influxdb > buckets > prices-bucket > if there is data + +![Docker](assets/img/doc-docker.png) + +- Verify the logs of the Workers container to see if candles are well inserted +- It may take some time to see the candles in the front-end + +## Setup default strategies, scenario and money management +- Go to Managing.Api swagger ```https://localhost/index.html``` +- Execute ```POST /settings``` +![Api Settings](assets/img/doc-settings.png) + +# Dev +## Generate Api Client +- Use [NSwag](https://github.com/RicoSuter/NSwag) +- Generate a Typescript client with Fetch template +- Deposit the file in ```Managing.WebApp\src\generated\``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..354145c --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# Introduction + +Managing App is a bot management application written using C# and Typescript. First goal was to be able to run an infinite +number of Bot without any limitations (will depend on your server capabilities only) +It is designed to support multiples exchanges and be controlled via webUI or Discord. +It contains bot management, backtesting, scenario management and money management.. + +--- + +# Roadmap + +## v1 - The base +- [x] Bot management +- [x] Backtesting +- [x] MoneyManagement +- [x] Strategies systems +- [x] WebUI +- [x] Robust trading management +- [x] Adaptive trading +- [x] Account management +- [x] Workers (prices, backtests, volumes) + +## v2 - The custody back +- [x] Web3 Authentification +- [x] Hot-wallets management +- [x] Chainlink and Subgraphs feeds +- [x] [GMX Contracts integration](https://gmx.io/) + +### v2.1 +- [x] Tools : RektFees, Spotlight +- [ ] Address tracking +- [ ] Strategies optimisation +- [ ] Trading desk +- [ ] Metrics (backtests, portofolio) +- [ ] Enhance performances +- [ ] Dockerize everything + +### v2.2 +- [ ] Hedging Bot with Options [HegicOption](https://www.hegic.co/) +- [ ] Market making + +## v3 - The bitcoin protocol +- [ ] Integrate [LNMarkets](https://lnmarkets.com/) + +## v4 - The omnipotence +- [ ] Connect to [Blockstream Satellite](https://blockstream.com/satellite/) + +# Stack + +## Architecture +![alt text](assets/img/Architecture.png) + +## Front-end +- [Vite.JS](https://vitejs.dev/) +- [Tailwindcss](https://tailwindcss.com/) +- [Daisy UI](https://daisyui.com/) +- [HeroIcon](https://heroicons.com/) +- [Toastify](https://fkhadra.github.io/react-toastify/introduction) +- [Tradingview Lightweight-charts](https://github.com/tradingview/lightweight-charts) +- [Ploty](https://github.com/plotly/react-plotly.js) +- [SignalR](https://github.com/dotnet/aspnetcore/tree/main/src/SignalR#readme) +- [Wagmi](https://wagmi.sh/) +- [Connectkit](https://github.com/family/connectkit) + +## Back-end +- .NET 7 +- [SignalR](https://dotnet.microsoft.com/en-us/apps/aspnet/signalr) +- [Discord.Net](https://github.com/discord-net/Discord.Net) +- [CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net) +- [Stock Indicators](https://daveskender.github.io/Stock.Indicators/) +- [InfluxDb](https://github.com/influxdata/influxdb-client-csharp) +- [Nethereum](https://github.com/Nethereum/Nethereum) + +--- + +# Features +## Money Management +- Create a defined money management for a given timeframe (StopLoss, TakeProfit, Amount to risk) +- Edit a money management configuration +- Delete a configuration + +## Strategies +- Build a strategy +- Delete strategy + +Strategies availables : + +| Strategy | Description | Recommanded values | +| ----------- | ----------- | ----------- | +| RsiDivergence | Trigger a signal when a divergence occurs on the period | Period : 4 for 6| +| RsiDivergenceConfirm | First, detect a divergence and trigger a signal when the divergence is confirmed. The confirmation happen for a LONG when the price close above the candle in divergence. The loopback period is based on the period parameter. | Period : 4 for 6| +| MACDCross | Trigger a signal when EMAs cross | FastPeriod : 12, SlowPeriods : 26, SignalPeriods : 9| +| SuperTrend | Trigger a SHORT signal when previous candle is above the super trend and the last candle close below the super trend | Period : 10, Multiplier : 3 | +| ChandelierExit | Trigger a SHORT signal when previous candle is above the ChandelierExit and the last candle close below the ChandelierExit | Period : 22, Multiplier : 3 | +| EMACross | Trigger a signal when last candle cross the EMA | Period : 200 | +| EMATrend | Return a Trend signal SHORT when last candle is below the EMA and return a Trend LONG signal when StochRSI < 20% | Period : 200 | +| StochRsiTrend | Return a Trend signal SHORT when Stoch RSI > 80% and return a Trend LONG signal when StochRSI < 20% | Period : 22 | +| STC | Return a signal SHORT when previous STC > 75% and current STC <= 75% | Period : 22 | + +## Scenarios +- Build a scenario with multiple strategies +- Delete a scenario + +## Backtests + +The backtest system works with multiple required parameters : +- Exchange (Binance, Kraken, FTX) +- Ticker (ADAUSDT, BTCUSDT, etc..) +- Days : Since when did you want to start backtest. Should be a negative value +- ScenarioName +- Timeframe (OneDay, FifteenMinutes, etc..) +- BotType (ScalpingBot or FlippingBot) +- Initial balance + +## Bots + +- Create and run a bot +- Stop / Restart a bot +- Delete a bot +- Stop all bots +- Set bot to watch only (send signal to discord instead of opening a new position) + +Bot types availables : + +| Type | Description | +| ----------- | ----------- | +| ScalpingBot | This bot will open position and wait before opening a new one | +| FlippingBot | The flipping bot flipping the position only when a strategy trigger an opposite signal | + diff --git a/assets/NSwagConfig.nswag b/assets/NSwagConfig.nswag new file mode 100644 index 0000000..df2ce56 --- /dev/null +++ b/assets/NSwagConfig.nswag @@ -0,0 +1,75 @@ +{ + "runtime": "Net60", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "url": "https://localhost:5001/swagger/v1/swagger.json", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToTypeScriptClient": { + "className": "{controller}Client", + "moduleName": "", + "namespace": "", + "typeScriptVersion": 4.3, + "template": "Fetch", + "promiseType": "Promise", + "httpClass": "HttpClient", + "withCredentials": false, + "useSingletonProvider": false, + "injectionTokenType": "OpaqueToken", + "rxJsVersion": 6.0, + "dateTimeType": "Date", + "nullValue": "Undefined", + "generateClientClasses": true, + "generateClientInterfaces": false, + "generateOptionalParameters": false, + "exportTypes": true, + "wrapDtoExceptions": false, + "exceptionClass": "ApiException", + "clientBaseClass": null, + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "protectedMethods": [], + "configurationClass": null, + "useTransformOptionsMethod": false, + "useTransformResultMethod": false, + "generateDtoTypes": true, + "operationGenerationMode": "MultipleClientsFromFirstTagAndOperationId", + "markOptionalProperties": true, + "generateCloneMethod": false, + "typeStyle": "Interface", + "enumStyle": "Enum", + "useLeafType": false, + "classTypes": [], + "extendedClasses": [], + "extensionCode": null, + "generateDefaultValues": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateConstructorInterface": true, + "convertConstructorInterfaceData": false, + "importRequiredTypes": true, + "useGetBaseUrlMethod": false, + "baseUrlTokenName": "API_BASE_URL", + "queryNullValue": "", + "useAbortSignal": false, + "inlineNamedDictionaries": false, + "inlineNamedAny": false, + "includeHttpContext": false, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "Managing.WebApp/src/generated/", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/assets/Todo-v1.txt b/assets/Todo-v1.txt new file mode 100644 index 0000000..8d845da --- /dev/null +++ b/assets/Todo-v1.txt @@ -0,0 +1,151 @@ +Principals features : +- Create X bots +- Each Trading bot can have multiple strategies +- Each Strategy is run for every new candle that the bot read from the database +- Use exchange worker to retrieve candles for a ticker +- Open one click a long with SL and 2 take profit with defined risk + +V1 : + +Part 1 : Running bot +- OK - Create no relationnal database to store all tickers history by exchanges +- OK - Feed the database with a Bot +- OK - On Bot motherclass, create a method accessible to child to retrieve the last candle since a prompted date +- OK - Create 3 Soldiers strategy +- OK - Create rule "CloseHigherThanThePreviousHigh" +- OK - Create rule "CloseLowerThanThePreviousLow" + +Part 2 : Make a trade +- OK - Implement a GetBalance with the value in $ +- OK - Calculate risk per for a given trade +- OK - Open a Long with defined risk level and exchange +- OK - Define and Open Stop loss with risk level +- OK - Define and open 2 Take Profit with risk level +- OK - Register generic candle for every exchanges in database +- OK - Create an endpoint to retrieve all order (with a filter class) +- OK - Create an endpoint to close an order for a given ExchangeOrderId +- OK - Implement OpenMarketTrade for Binance Futures +- OK - Implement GetPrice for Binance Futures +- OK - Implement GetTrade for Binance Futures +- OK - Binance : Handle TimeInForce for limit or market trade + +Part 3 : Bot running RSI +- OK - Create RSI for 32period +- OK - Handle multiple divergence point for a candle range +- OK - Create rule to find a divergence for given RSI -> Confidence.High +- OK - Create an identifier for each bot based on name-datetime +- OK - Save price into Signal Object + +Part 3.1 +- OK - Each bot should be a manager keep in memory the trades executed and execute trade by calling openMarketPosition() +- OK - Create a mother class call TradeManager +- OK - This class should access to the signal return by the Scalping bot +- OK - This class should handle the trade management +- OK - Save Signals into database + +Part 3.2 : +- OK - Create a unit test to backtest the bot execution +- OK - Get range candle list for a ticker +- OK - Each bot should have a backtest mode. This mode allow the caller to insert his own candles into the list. This mode will be used by the unit tests +- OK - Foreach candle, update the candle list of the bot and implicitly run all strategies + +- OK - For each last signal for a ticker check if position is open. If not: Open position. +- OK - If an position is already open, update the position and check the price and trigger a close/flip if there is an inversed signal for that position. +- OK - If there is a inverted signal, close or flip the position depending of the confidence of the signal +- OK - Create a collection for registering the position with pnl, status +- OK - Save in trade table a reference to the position id, bot identifier +- OK - Create class to calculate PNL +- OK - openMarketPosition () should handle a parameter to make paper trading: just return the object that represent the trade executed by the exchange. That parameter will be set by bot in backtest mode +- OK - Add function to reopen trade that wasnt filled or whatever +- OK - Assert the fact that a list of positions have been created and ending in positive +- OK - Fix timestamp unix for getting klines for OneMinutes timeframe +- OK - Run bot on past candle +- OK - Extract trading logic from Bot class, to create a (TradingBot : Bot) class. + +Part 4: +- Ok - Create RSIBearishDivergenceStrategy +- OK - Move Risk level config into appsettings to manipulate easily the user preference +- OK - Implement multi-timeframe to Define MoneyManagement for everyTimeFrame +- OK - Handle little quantity for trade by multiplying the quantity inside pnl object +- OK - Set the correct quantity for a trade who take profit #1 +- OK - Add more tests for ProfitAndLoss object +- OK - Setup optionnal second take profit +- OK - Create DailyBot and Minutes bot to handle 5 ans 14 periods rsi +- OK - Run Bot for little TimeFrame +- OK - Implement GetTrade for an OrderId on Binance +- OK - Add discord connection +- OK - Expose all the API on discord bot +- OK - Run instance of bot for ADA, 5 periods, 15min +- OK - Refactoring bots unit tests to use same method than discord bot +- OK - Create CSV file for backtesting report + +Part 5: +- OK - Improve open position from discord +- OK - Improve multi-risk levels handling +- OK - Improve divergence signal by handeling confidence +- OK - Implement ExchangeService.CloseTrade for exchanges + +Part 6: webapp +- OK - Build front-end app with react +- OK - Build Tradingview component +- Update trading bot from signalr for everynew run => pass the BotHubContext to ITradingbot interface +- OK - Bots page to manage running bots +- - OK - Create a modal to start a bot +- - OK - On backtest page, display a button to start a bot based on defined backtest configuration +- - Handle stop and restart +- OK - Create scanner page to load all the ticker backtest base on a setup (timeframe, risk, day, bottype) +- OK - Save backtestResult into database to display it into Scanner page +- OK - Display backtest result on scanner page +- Scenario builder => A component to build nested trading strategy https://www.kindacode.com/article/react-typescript-drag-and-drop/ +- - OK - Store scenario into database -> a scenario contain a list of defined strategy +- - OK - Dynamically insert scenario into ITradingBot +- - OK - Create a modal form to build a scenario that contains a list of strategies +- - OK - On backtest page, retrieve scenario list to launch a backtest based on it + +Part 7: Improve trading +- OK - Handle fees +- OK - Create strategy to send rsi div signal on confirmation only +- OK - Make flipping position optionnal +- OK - Candle mapped on the wrong time => 18h30 should be 18h15 +- KO - Enable backtesting on very long time range => No historical data more than 1500ohlc per timeframe on FTX + +Part 8 : enhance front +- Settings page => + - OK - Put money management into database to be able to modify it without rebuild/restart the api + - OK - Create a tab page on settings page to easily update monymanagement in db +- Improve card informations for bots +- - OK - Display the signals count for long and short +- - OK - Add button to toggle start and stop +- - OK - Add a toggle for isWatchingOnly +- OK - Add filter checkbox on Backtest/Moneymanagement to filter the list +- OK - Create a button to delete all backtests +- OK - Implement switch network debug/local + +Part 9: Improve bot usage +- Save bot config to database to re-run bot on restart +- OK - Create endpoint to restart a bot +- Ok - Create endpoint to stop all bots +- OK - On signal, if a position is open => Same direction : expired signal | opposition direction : Flip open position +- OK - Implement a flipping function on an open position +- Ok - Track balance evolution to determine max drawdown +- OK - Determine best parameter for a given scenario : https://www.youtube.com/watch?v=jm-UfVPqUQo (Start with a unit test) +- OK - When a bot start up for a ticker, clean all limit order +- OK - When a bot start up for a ticker, clean open position +- OK - Before opening a trade clean limit order +- OK - Get status of position before closing it. The position might be already close by the exchange + +Part 10: Improve performance +- OK - Calculate the strategies only on the last n*10 period +- OK - Merge divergence RSI strategy to calculate rsi only once by run +- KO - Prevent Order's notional must be no smaller than 5.0 +- OK - Don't run update Signal and manage position if no new candle + +Part 11: Extra +- OK - Create custom exception to handle error +- OK - Add ELK stacks +- OK - Display R/R used on backtestResult +- OK - Update Exchanges libraries +- OK - Integrate FTX exchange +- OK - Display wallet evolution +- OK - Remove API keys from git +- OK - Add seed for moneymanagement and scenario diff --git a/assets/Todo-v2.md b/assets/Todo-v2.md new file mode 100644 index 0000000..8f4c220 --- /dev/null +++ b/assets/Todo-v2.md @@ -0,0 +1,199 @@ +# V2 +## Done +### Adaptive mode +- [x] Create a risk mode 'Adaptive' to be able to not use save moneymanagement settings +- [x] Adaptive mode will force Bot to determine SL by getting the previous highest price on the period (prevent choppy price on good entry) +- [x] Create account (type, exchange, key, secret) +- [x] Update trading bot to handle one account +- [x] Remove exchange from bot. Exchange will be set on the account +- [x] Update ftxClient to not use Managingbot subaccount +- [x] Update exchangeService to handle account +- [x] Dynamic Ticker selection base on best volumes on the exchanges +- [x] Create endpoint to get the ticker list for the frontend +- [x] Create method in application to get best bid and best ask +- [x] Submit only limit with IOC +- [x] Implement Poly to have a retry policy for limit order ioc execution => Unsure position is open +- [x] For openOrder inject a parameter to select the ioc mode. If risk is adaptive we just place the order a the price without forcing the position opening +- [x] Integrate STC signals +### Web3 +- [x] Integrate wallet management from backend side +- [x] Integrate wagmi/connectkit +- [x] Sign message with connectkit +- [x] On wallet connect, check if token is not expired +- [x] User can have multiple address +- [x] User can create an account base on evm exchange +- [x] Hide key/secret if evm exchange is selected +- [x] Generate pub/priv key on evm account creation and store it into mongo +- [x] Get balance for an address +- [x] Handle multi-chain +- [x] Add arbitrum testnet for backend +- [x] User can withdraw found to the connected address of the user +- [x] Implement get balance with chainlink feed +- [x] Abstract subgraph management +- [x] Get price from multiple price feeds subgraph +- [x] Add to ExchangeService GetAvailableTicker() +- [x] Add get token list method to EvmManager +- [x] Setup worker to save candles into db +- [x] Open trade -> CreateIncreaseOrder +- [x] Close trade -> CreateDecreasePosition +- [x] Close all orders +- [x] Create SL & TP -> CreateDecreaseOrder +- [x] Get Fees -> Estimate Tx cost +- [x] Adapt GetBalance to retrieve stablecoin amount +- [x] Create worker to fetch GMX leaderboard -> LeaderboardWorker +- [x] Leaderboard will create PositionUpdate message bus to deposit a message with all information to open a trade +- [x] Create bot type CopyTrading to receive PositionUpdate bus message and open position based on criteria +- [x] CopyTrading bot will send an OpenPosition button on Discord if he's set to watchOnly +- [x] Fix : Trim address, give name to address, decrease amount bug, +- [x] Display best last backtests ran by backtester workers +- [x] Refactor GetAccounts/GetAccount to handle the user +- [x] AccountType : Binance/EvmTrador/EvmWatch/EvmPersonal +- [x] Make better abstration of the Database projects (influx, mongodb) to Remove the link between Application and Infrastructure +- [x] Implement strategy pattern for exchange processors +- [x] Use cache for position and signal +- [x] Don't run update Signal and manage position if no new candle +### Workers +- [x] Add Influxdb +- [x] Create workers for scrap data (per ticker, per exchange, per timeframe) +- [x] Use candle from influxdb for backtests and bots +- [x] Create a worker that get a daily top 15 of ticker volume +- [x] Scrap candle only for crypto with high volume +- [x] Create a worker that run backtest and save it in db + +### Front-end +- [x] Return list of balances with tokens/chains data +- [x] On account line click, display tokens by chain available +- [x] Call Available ticker to produce the droplist +- [x] Display multipanes https://github.com/qwpto/lightweight-charts-panels / https://codesandbox.io/s/react-typescript-forked-mxek17?file=/src/App.tsx / https://github.com/Rassibassi/multipane_tradingview +- [x] Display last open position with lines (add SL and TP) +- [x] Fix double charts problem (has been fixed by removing strict mode) +- [x] Try to pass the tailwind current theme color to the chart +- [x] List all open position for every trading account +- [x] Create button to force close position +- [x] Open Position button +- [x] Improve position message -> Create message buildor +- [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 + +# The rest +## Part 1 : Adaptive trading +- [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 +- [ ] Strategy can return a suggested trade that will give the best orders to execute + +## Part 2 : Workflows builder +- [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 +## 1 - Wallet management +- [ ] User can deposit token to and evm account with a QR code or clicking on button to copy the address +## 2 - [Track address](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32158941) +- [ ] Create a snapshot of the account balances with BalancesWorker +- [ ] 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) +- [ ] Save theme into database +- [ ] 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] Live data position hub +- [x] Live candles +- [x] Open position from desk +## 5 - Live liquidation map +- [ ] Create worker to aggregate binance/okx liquidation +- [ ] Render liquidation map with a hub subscription to get live update +- [ ] Live data signal +- [ ] Display massive liquidation on TradeChartWidget +## 6 - Metrics [Link](https://github.com/users/CryptoOda/projects/1/views/1?pane=issue&itemId=32159107) +- [ ] Create a worker that get top 5 cryptos by scenario +- [ ] Create worker to store portofolio evolution +- [ ] Display portofolio evolution +- [ ] Create a worker to scrap funding fees over time (per ticker, per exchange) +## 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 +- [ ] 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 +- [ ] Store webapp into docker +- [ ] 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 +- [ ] 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 + +# Front-end +## Improve Account page +- [ ] Display total balances (only trader account) on Settings->Accounts Page +- [ ] On Token Account details line -> Add button to withdraw money (this will open a Modal to prompt the address/amount that will receive the money) +## Charts +- [ ] Add multi-timeframe options +- [ ] Display optionnal indicators on charts +- [ ] Display current selected position (when i click on a Position in the list) +## Trading desk +- [ ] WAIT - Create a button to open a hedge on option market +- [ ] Get global exposure ((total position / total balances *) 100) +- [ ] Display multiple tab to show different tickers +## Scenarios +- [x] Create a blocky-like editor to create scenarios https://google.github.io/blockly-samples/ +## 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 : +- [ ] Implement alias to reduce 'import' lines https://github.com/subwaytime/vite-aliases +- [ ] Make Get Accounts List as a store +- [ ] Replace all html-tag Modals (ex: BacktestPlayground.tsx) +- [ ] Refactoring Components : +- organism : Put all the code related to the app (Trade charts etc). This folder should be delete when the project is forked +- 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 + +- [ ] Remove mediator +- [ ] 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 Microsoft Orleans diff --git a/assets/documentation/Architecture.drawio b/assets/documentation/Architecture.drawio new file mode 100644 index 0000000..3d7e070 --- /dev/null +++ b/assets/documentation/Architecture.drawio @@ -0,0 +1,1586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/documentation/EndGame.md b/assets/documentation/EndGame.md new file mode 100644 index 0000000..66a7c87 --- /dev/null +++ b/assets/documentation/EndGame.md @@ -0,0 +1,12 @@ +```mermaid +flowchart TD + A[Futures bot] -->|50% weekly withdraw| B[Delta neutral bot] + A -->|25% weekly withdraw| F[Stake GMX] + A -->|25% weekly withdraw| E + B -->|25% profit | A + B -->|75% compounding| B + D[Hedging bot] -->|hedge open position| A + D -->|generate profit| A + E[Staking bot] -->|claim ETH rewards| F[Personnal Wallet] + +``` \ No newline at end of file diff --git a/assets/documentation/Flows.md b/assets/documentation/Flows.md new file mode 100644 index 0000000..c351076 --- /dev/null +++ b/assets/documentation/Flows.md @@ -0,0 +1,27 @@ +```mermaid +classDiagram + Workflow <|-- Flow + class Workflow{ + String Name + Usage Usage : Trading|Task + Flow[] Flows + String Description + } + class Flow{ + String Name + CategoryType Category + FlowType Type + FlowParameters Parameters + String Description + FlowType? AcceptedInput + OutputType[]? Outputs + Flow[]? ChildrenFlow + Flow? ParentFlow + Output? Output : Signal|Text|Candles + MapInput(AcceptedInput, ParentFlow.Output) + Run(ParentFlow.Output) + LoadChildren() + ExecuteChildren() + } + +``` \ No newline at end of file diff --git a/assets/documentation/PositionWorkflow.md b/assets/documentation/PositionWorkflow.md new file mode 100644 index 0000000..e58bc05 --- /dev/null +++ b/assets/documentation/PositionWorkflow.md @@ -0,0 +1,25 @@ +```mermaid +graph LR +    subgraph Position +        A(New) +        F +        G(Filled) --> L(Flipped) +        G --> H(Finished) +        A --> I(Cancelled) +        A --> J(Rejected) +    end + +    subgraph Trade Open +        A --> N +        N(Requested) --> O(Filled) +        O --> F(Partilly Filled) +    end + +    subgraph Trades SL/TP +        F --> B(PendingOpen)   +        B -->C(Requested) +        C -->P(Cancelled) +        C -->D[Filled] +        D --> G +    end +``` \ No newline at end of file diff --git a/assets/img/Architecture.png b/assets/img/Architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..8490956300a462c6b8e85aa9c02387a21341b165 GIT binary patch literal 203872 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 Z6)?EFtwbYWpi%(-nQpN#&N8Bf{SSO-&?Eo= literal 0 HcmV?d00001 diff --git a/assets/img/doc-docker.png b/assets/img/doc-docker.png new file mode 100644 index 0000000000000000000000000000000000000000..f83c43a5080354581d4f2597c3e8c26cb176783b GIT binary patch literal 20522 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∾r?JT-1L7$_cF{Sm_yt`nB3=K1+j2 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 W5HzW-hj%=`rJ<&yTBd9j{Qm&m(*ZdE literal 0 HcmV?d00001 diff --git a/assets/img/doc-influxdb-apikeys-2.png b/assets/img/doc-influxdb-apikeys-2.png new file mode 100644 index 0000000000000000000000000000000000000000..29a225f79f466eba3041d0e660c0c19774dcf0ee GIT binary patch literal 41684 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 Bry>9V literal 0 HcmV?d00001 diff --git a/captain-definition b/captain-definition new file mode 100644 index 0000000..4ba5158 --- /dev/null +++ b/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "dockerfilePath": "src/Managing.Api/Dockerfil" +} diff --git a/scripts/build_and_run.sh b/scripts/build_and_run.sh new file mode 100644 index 0000000..be69230 --- /dev/null +++ b/scripts/build_and_run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Navigate to the src directory +cd ../src + +# Build the managing.api image +docker build -t managing.api -f Managing.Api/Dockerfile . --no-cache + +# Build the managing.api.workers image +docker build -t managing.api.workers -f Managing.Api.Workers/Dockerfile . --no-cache + +# Start up the project using docker-compose +docker-compose -f Managing.Docker/docker-compose.yml -f Managing.Docker/docker-compose.sandbox.yml up -d \ No newline at end of file diff --git a/scripts/clean-front-end-code.cmd b/scripts/clean-front-end-code.cmd new file mode 100644 index 0000000..a2c3272 --- /dev/null +++ b/scripts/clean-front-end-code.cmd @@ -0,0 +1,4 @@ +cd .. +cd .\src\Managing.WebApp\ +npm run lint:fix +npm run prettier \ No newline at end of file diff --git a/scripts/docker-deploy-sandbox - Copy.cmd b/scripts/docker-deploy-sandbox - Copy.cmd new file mode 100644 index 0000000..e554517 --- /dev/null +++ b/scripts/docker-deploy-sandbox - Copy.cmd @@ -0,0 +1,5 @@ +cd .. +cd .\src\ +docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache +docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache +docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d \ No newline at end of file diff --git a/scripts/docker-deploy-sandbox.cmd b/scripts/docker-deploy-sandbox.cmd new file mode 100644 index 0000000..e554517 --- /dev/null +++ b/scripts/docker-deploy-sandbox.cmd @@ -0,0 +1,5 @@ +cd .. +cd .\src\ +docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache +docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache +docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d \ No newline at end of file diff --git a/scripts/docker-redeploy-oda.cmd b/scripts/docker-redeploy-oda.cmd new file mode 100644 index 0000000..8b6cfa5 --- /dev/null +++ b/scripts/docker-redeploy-oda.cmd @@ -0,0 +1,22 @@ +cd .. +cd .\src\ +ECHO "Stopping containers..." +docker stop sandbox-managing.api-1 +docker stop sandbox-managing.api.workers-1 +ECHO "Contaiters stopped" +ECHO "Removing containers..." +docker rm sandbox-managing.api-1 +docker rm sandbox-managing.api.workers-1 +ECHO "Containers removed" +ECHO "Removing images..." +docker rmi managing.api +docker rmi managing.api:latest +docker rmi managing.api.workers +docker rmi managing.api.workers:latest +ECHO "Images removed" +ECHO "Building images..." +docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache +docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache +ECHO "Deploying..." +docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d +ECHO "Deployed" \ No newline at end of file diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1 @@ + diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..cb4f5d0 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0008: Use explicit type +dotnet_diagnostic.IDE0008.severity = none diff --git a/src/Managing.Api.Workers.csproj b/src/Managing.Api.Workers.csproj new file mode 100644 index 0000000..c3d9236 --- /dev/null +++ b/src/Managing.Api.Workers.csproj @@ -0,0 +1,45 @@ + + + + net7.0 + enable + AnyCPU;x64 + 3900ce93-de15-49e5-9a61-7dc2209939ca + Linux + ..\.. + ..\..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + diff --git a/src/Managing.Api.Workers/Controllers/WorkerController.cs b/src/Managing.Api.Workers/Controllers/WorkerController.cs new file mode 100644 index 0000000..3a5bc48 --- /dev/null +++ b/src/Managing.Api.Workers/Controllers/WorkerController.cs @@ -0,0 +1,31 @@ +using Managing.Application.Workers.Abstractions; +using Managing.Domain.Workers; +using Microsoft.AspNetCore.Mvc; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Controllers; + +[ApiController] +[Route("[controller]")] +[Produces("application/json")] +public class WorkerController : ControllerBase +{ + private readonly IWorkerService _workerService; + + public WorkerController(IWorkerService workerService) + { + _workerService = workerService; + } + + [HttpGet] + public ActionResult> GetWorkers() + { + return Ok(_workerService.GetWorkers()); + } + + [HttpPatch] + public async Task ToggleWorker(WorkerType workerType) + { + return Ok(await _workerService.ToggleWorker(workerType)); + } +} diff --git a/src/Managing.Api.Workers/Dockerfile b/src/Managing.Api.Workers/Dockerfile new file mode 100644 index 0000000..9e6e534 --- /dev/null +++ b/src/Managing.Api.Workers/Dockerfile @@ -0,0 +1,36 @@ +# Use the official Microsoft ASP.NET Core runtime as the base image. +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +# Use the official Microsoft .NET SDK image to build the code. +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /src +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/"] +COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"] +COPY ["Managing.Application.Workers/Managing.Application.Workers.csproj", "Managing.Application.Workers/"] +COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"] +COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"] +COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"] +RUN dotnet restore "Managing.Api.Workers/Managing.Api.Workers.csproj" +COPY . . +WORKDIR "/src/Managing.Api.Workers" +RUN dotnet build "Managing.Api.Workers.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Managing.Api.Workers.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +COPY Managing.Api.Workers/managing_cert.pfx . +COPY Managing.Api.Workers/appsettings.Lowpro.json ./appsettings.json +ENTRYPOINT ["dotnet", "Managing.Api.Workers.dll"] diff --git a/src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs b/src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs new file mode 100644 index 0000000..063f4ae --- /dev/null +++ b/src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs @@ -0,0 +1,20 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Managing.Api.Workers.Filters +{ + public class EnumSchemaFilter : ISchemaFilter + { + public void Apply(OpenApiSchema model, SchemaFilterContext context) + { + if (context.Type.IsEnum) + { + model.Enum.Clear(); + Enum.GetNames(context.Type) + .ToList() + .ForEach(n => model.Enum.Add(new OpenApiString(n))); + } + } + } +} diff --git a/src/Managing.Api.Workers/Managing.Api.Workers.csproj b/src/Managing.Api.Workers/Managing.Api.Workers.csproj new file mode 100644 index 0000000..b3b1706 --- /dev/null +++ b/src/Managing.Api.Workers/Managing.Api.Workers.csproj @@ -0,0 +1,45 @@ + + + + net7.0 + enable + AnyCPU;x64 + 3900ce93-de15-49e5-9a61-7dc2209939ca + Linux + ..\.. + ..\..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs new file mode 100644 index 0000000..cd36f40 --- /dev/null +++ b/src/Managing.Api.Workers/Program.cs @@ -0,0 +1,150 @@ +using System.Text.Json.Serialization; +using Managing.Api.Workers.Filters; +using Managing.Api.Workers.Workers; +using Managing.Api.WorkersExceptions; +using Managing.Application.Hubs; +using Managing.Bootstrap; +using Managing.Common; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Managing.Infrastructure.Databases.MongoDb; +using NSwag; +using NSwag.Generation.Processors.Security; +using Serilog; +using Serilog.Sinks.Elasticsearch; + +// Builder +var builder = WebApplication.CreateBuilder(args); +builder.Configuration + .AddEnvironmentVariables(); + +builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => +{ + var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-"); + var indexFormat = $"managing-worker-{envName}-" + "{0:yyyy.MM.dd}"; + var yourTemplateName = "dotnetlogs"; + var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"])) + { + IndexFormat = indexFormat.ToLower(), + AutoRegisterTemplate = true, + OverwriteTemplate = true, + TemplateName = yourTemplateName, + AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, + TypeName = null, + BatchAction = ElasticOpType.Create, + MinimumLogEventLevel = Serilog.Events.LogEventLevel.Information, + FailureCallback = e => Console.WriteLine($"Unable to submit event {e.RenderMessage()} to ElasticSearch. " + + $"Full message : " + e.Exception.Message), + DetectElasticsearchVersion = true, + RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, + }; + + loggerConfiguration + .WriteTo.Console() + .WriteTo.Elasticsearch(es); +}); +builder.Services.AddOptions(); +builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.MongoDb)); +builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); +builder.Services.AddControllers().AddJsonOptions(options => + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); +builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder => +{ + builder + .SetIsOriginAllowed((host) => true) + .AllowAnyOrigin() + .WithOrigins("http://localhost:3000/") + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); +})); +builder.Services.AddSignalR().AddJsonProtocol(); + +builder.Services.RegisterWorkersDependencies(builder.Configuration); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddOpenApiDocument(document => +{ + document.AddSecurity("JWT", Enumerable.Empty(), new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.ApiKey, + Name = "Authorization", + In = OpenApiSecurityApiKeyLocation.Header, + Description = "Type into the textbox: Bearer {your JWT token}." + }); + + document.OperationProcessors.Add( + new AspNetCoreOperationSecurityScopeProcessor("JWT")); +}); +builder.Services.AddSwaggerGen(options => +{ + options.SchemaFilter(); + options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme + { + Description = "Please insert your JWT Token into field : Bearer {your_token}", + Name = "Authorization", + Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http, + In = Microsoft.OpenApi.Models.ParameterLocation.Header, + Scheme = "Bearer", + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement{ + { + new Microsoft.OpenApi.Models.OpenApiSecurityScheme{ + Reference = new Microsoft.OpenApi.Models.OpenApiReference{ + Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[]{} + } + }); +}); + +builder.WebHost.SetupDiscordBot(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); + +// App +var app = builder.Build(); +app.UseSerilogRequestLogging(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseOpenApi(); +app.UseSwaggerUi3(); +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing Workers v1"); + c.RoutePrefix = string.Empty; +}); + +app.UseCors("CorsPolicy"); + +app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware)); + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.UseEndpoints(endpoints => +{ + endpoints.MapControllers(); + endpoints.MapHub("/positionhub"); +}); + +app.Run(); diff --git a/src/Managing.Api.Workers/Workers/BaseWorker.cs b/src/Managing.Api.Workers/Workers/BaseWorker.cs new file mode 100644 index 0000000..cc11430 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/BaseWorker.cs @@ -0,0 +1,75 @@ + +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers; + +public abstract class BaseWorker : BackgroundService where T : class +{ + private readonly WorkerType _workerType; + protected readonly ILogger _logger; + protected readonly TimeSpan _delay; + private readonly IWorkerService _workerService; + private int _executionCount; + + protected BaseWorker( + WorkerType workerType, + ILogger logger, + TimeSpan timeSpan, + IWorkerService workerService) + { + _workerType = workerType; + _logger = logger; + _delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan; + _workerService = workerService; + _executionCount = 0; + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + try + { + _logger.LogInformation($"[{_workerType}] Starting"); + var worker = await _workerService.GetWorker(_workerType); + + if (worker == null) + { + await _workerService.InsertWorker(_workerType, _delay); + } + else + { + _logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}"); + _executionCount = worker.ExecutionCount; + } + + cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping")); + + while (!cancellationToken.IsCancellationRequested) + { + worker = await _workerService.GetWorker(_workerType); + + //if (true) + if (worker.IsActive) + { + await Run(cancellationToken); + _executionCount++; + await _workerService.UpdateWorker(_workerType, _executionCount); + _logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}"); + } + else + { + _logger.LogInformation($"[{_workerType}] Worker not active. Next run at : {DateTime.UtcNow.Add(_delay)}"); + } + + await Task.Delay(_delay); + } + _logger.LogInformation($"[{_workerType}] Stopped"); + } + catch (Exception ex) + { + _logger.LogError($"Error : {ex.Message}"); + } + } + + protected abstract Task Run(CancellationToken cancellationToken); +} diff --git a/src/Managing.Api.Workers/Workers/FeeWorker.cs b/src/Managing.Api.Workers/Workers/FeeWorker.cs new file mode 100644 index 0000000..a0aa579 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/FeeWorker.cs @@ -0,0 +1,29 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class FeeWorker : BaseWorker +{ + private readonly ITradingService _tradingService; + private static readonly WorkerType _workerType = WorkerType.Fee; + + public FeeWorker( + ILogger logger, + ITradingService tradingService, + IWorkerService workerService) : base( + _workerType, + logger, + TimeSpan.FromHours(12), + workerService + ) + { + _tradingService = tradingService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + _tradingService.UpdateFee(TradingExchanges.Evm); + } +} diff --git a/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs b/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs new file mode 100644 index 0000000..96feb0e --- /dev/null +++ b/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs @@ -0,0 +1,28 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class LeaderboardWorker : BaseWorker +{ + private readonly IStatisticService _statisticService; + private static readonly WorkerType _workerType = WorkerType.LeaderboardWorker; + + public LeaderboardWorker( + ILogger logger, + IStatisticService statisticService, + IWorkerService workerService) : base( + _workerType, + logger, + TimeSpan.FromHours(24), + workerService + ) + { + _statisticService = statisticService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + await _statisticService.UpdateLeaderboard(); + } +} diff --git a/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs b/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs new file mode 100644 index 0000000..13aa38a --- /dev/null +++ b/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs @@ -0,0 +1,28 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class NoobiesboardWorker : BaseWorker +{ + private readonly IStatisticService _statisticService; + private static readonly WorkerType _workerType = WorkerType.Noobiesboard; + + public NoobiesboardWorker( + ILogger logger, + IStatisticService statisticService, + IWorkerService workerService) : base( + _workerType, + logger, + TimeSpan.FromHours(24), + workerService + ) + { + _statisticService = statisticService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + await _statisticService.UpdateNoobiesboard(); + } +} diff --git a/src/Managing.Api.Workers/Workers/PositionFetcher.cs b/src/Managing.Api.Workers/Workers/PositionFetcher.cs new file mode 100644 index 0000000..4e66188 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PositionFetcher.cs @@ -0,0 +1,37 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Hubs; +using Managing.Application.Workers.Abstractions; +using Microsoft.AspNetCore.SignalR; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PositionFetcher : BaseWorker +{ + private static readonly WorkerType _workerType = WorkerType.PositionFetcher; + private readonly ITradingService _tradingService; + private readonly IHubContext _hubContext; + private readonly ILogger _logger; + + public PositionFetcher( + ILogger logger, + IWorkerService workerService, + ITradingService tradingService, + + IHubContext hubContext) : base( + _workerType, + logger, + TimeSpan.FromSeconds(10), + workerService) + { + _logger = logger; + _tradingService = tradingService; + _hubContext = hubContext; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + var positions = _tradingService.GetPositions().Where(p => p.Initiator != PositionInitiator.PaperTrading); + await _hubContext.Clients.All.SendAsync("Positions", positions); + } +} diff --git a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs new file mode 100644 index 0000000..61811e5 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs @@ -0,0 +1,200 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Workers.Abstractions; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PositionManagerWorker : BaseWorker +{ + private static readonly WorkerType _workerType = WorkerType.PositionManager; + private readonly ITradingService _tradingService; + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ILogger _logger; + + public PositionManagerWorker( + ILogger logger, + IWorkerService workerService, + ITradingService tradingService, + IExchangeService exchangeService, + IAccountService accountService) : base( + _workerType, + logger, + TimeSpan.FromMinutes(1), + workerService) + { + _logger = logger; + _tradingService = tradingService; + _exchangeService = exchangeService; + _accountService = accountService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + await ManageNewPositions(); + await ManagePartillyFilledPositions(); + await ManageFilledPositions(); + } + + private async Task ManagePartillyFilledPositions() + { + var positions = GetPositions(PositionStatus.PartiallyFilled); + + _logger.LogInformation("Partilly filled positions count : {0} ", positions.Count()); + + 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); + + var account = await _accountService.GetAccount(position.AccountName, false, false); + + try + { + + 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; + + if (stopLoss != null & (stopLoss.Status == TradeStatus.Requested)) + { + position.StopLoss = stopLoss; + _logger.LogInformation("|_ SL is requested"); + } + else + { + throw new Exception("Stop loss not requested"); + } + } + else + { + _logger.LogInformation($"|_ SL is already handle. Current status = {position.StopLoss.Status}"); + } + + 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; + + if (takeProfit1 != null & (takeProfit1.Status == TradeStatus.Requested)) + { + position.TakeProfit1 = takeProfit1; + _logger.LogInformation("|_ TP is requested"); + } + else + { + throw new Exception("Take Profit 1 not requested"); + } + } + else + { + _logger.LogInformation($"|_ TP is already handle. Current status = {position.TakeProfit1.Status}"); + } + + if (position.TakeProfit2 != null && + position.TakeProfit2.Status == TradeStatus.PendingOpen) + { + var takeProfit2 = _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection, position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow).Result; + + if (takeProfit2 != null & (takeProfit2.Status == TradeStatus.Requested)) + { + position.TakeProfit2 = takeProfit2; + _logger.LogInformation("|_ TP2 is requested"); + } + else + { + throw new Exception("Take Profit 2 not requested"); + } + } + else + { + _logger.LogInformation("|_ TP2 is already handle or not required"); + } + } + catch (Exception ex) + { + _logger.LogError($"|_ Cannot fully filled position because : {ex.Message}"); + } + + if (position.StopLoss.Status == TradeStatus.Requested + && position.TakeProfit1.Status == TradeStatus.Requested + && (position.TakeProfit2 == null || position.TakeProfit2.Status == TradeStatus.Requested)) + { + position.Status = PositionStatus.Filled; + _logger.LogInformation($"|_ Position is now open and SL/TP are correctly requested"); + } + + _tradingService.UpdatePosition(position); + } + } + + private async Task ManageFilledPositions() + { + var positions = GetPositions(PositionStatus.Filled); + + _logger.LogInformation("Filled positions count : {0} ", positions.Count()); + + 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); + var account = await _accountService.GetAccount(position.AccountName, false, false); + + var updatedPosition = await _tradingService.ManagePosition(account, position); + _tradingService.UpdatePosition(updatedPosition); + } + } + + private IEnumerable GetPositions(PositionStatus positionStatus) + { + return _tradingService.GetPositionsByStatus(positionStatus) + .Where(p => p.Initiator != PositionInitiator.PaperTrading); + } + + private async Task ManageNewPositions() + { + var positions = GetPositions(PositionStatus.New); + + _logger.LogInformation("New positions count : {0} ", positions.Count()); + + foreach (var position in positions) + { + _logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker); + position.Status = PositionStatus.Updating; + _tradingService.UpdatePosition(position); + + // Update status if position is open since to long + if (position.Date < DateTime.UtcNow.AddDays(-2)) + { + position.Status = PositionStatus.Canceled; + _tradingService.UpdatePosition(position); + _logger.LogInformation($"|_ Position is now Canceled"); + continue; + } + + var account = await _accountService.GetAccount(position.AccountName, false, false); + if (!(await _exchangeService.GetOpenOrders(account, position.Ticker)).Any()) + { + position.Status = PositionStatus.Canceled; + _tradingService.UpdatePosition(position); + _logger.LogInformation($"|_ Position is now Canceled - Position close from exchange"); + continue; + } + + var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker); + + if (quantityInPosition <= 0) + { + position.Status = PositionStatus.New; + _logger.LogInformation("|_ Position is currently waiting for filling"); + } + else + { + position.Open.SetStatus(TradeStatus.Filled); + position.Status = PositionStatus.PartiallyFilled; + _logger.LogInformation($"|_ Position is now PartiallyFilled"); + } + + _tradingService.UpdatePosition(position); + } + } + +} diff --git a/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs b/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs new file mode 100644 index 0000000..d3816e2 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs @@ -0,0 +1,40 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public abstract class PricesBaseWorker : BaseWorker where T : class +{ + private readonly IPricesService _pricesService; + private readonly IStatisticService _statisticService; + private readonly Timeframe _timeframe; + + public PricesBaseWorker( + ILogger logger, + IPricesService pricesService, + IWorkerService workerService, + IStatisticService statisticService, + TimeSpan delay, + WorkerType workerType, + Timeframe timeframe) : base( + workerType, + logger, + delay, + workerService + ) + { + _pricesService = pricesService; + _statisticService = statisticService; + _timeframe = timeframe; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + var tickers = _statisticService.GetTickers(); + + foreach (var ticker in tickers) + { + await _pricesService.UpdatePrice(TradingExchanges.Evm, ticker, _timeframe); + } + } +} diff --git a/src/Managing.Api.Workers/Workers/PricesFifteenMinutesWorker.cs b/src/Managing.Api.Workers/Workers/PricesFifteenMinutesWorker.cs new file mode 100644 index 0000000..88df774 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesFifteenMinutesWorker.cs @@ -0,0 +1,23 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PricesFifteenMinutesWorker : PricesBaseWorker +{ + public PricesFifteenMinutesWorker( + ILogger logger, + IPricesService pricesService, + IStatisticService statisticService, + IWorkerService workerService) : base( + logger, + pricesService, + workerService, + statisticService, + TimeSpan.FromMinutes(1), + WorkerType.PriceFifteenMinutes, + Timeframe.FifteenMinutes + ) + { + } +} diff --git a/src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs b/src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs new file mode 100644 index 0000000..8dff08f --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs @@ -0,0 +1,23 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PricesFiveMinutesWorker : PricesBaseWorker +{ + public PricesFiveMinutesWorker( + ILogger logger, + IPricesService pricesService, + IStatisticService statisticService, + IWorkerService workerService) : base( + logger, + pricesService, + workerService, + statisticService, + TimeSpan.FromMinutes(2.5), + WorkerType.PriceFiveMinutes, + Timeframe.FiveMinutes + ) + { + } +} diff --git a/src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs b/src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs new file mode 100644 index 0000000..2b49931 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs @@ -0,0 +1,23 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PricesFourHoursWorker : PricesBaseWorker +{ + public PricesFourHoursWorker( + ILogger logger, + IPricesService pricesService, + IStatisticService statisticService, + IWorkerService workerService) : base( + logger, + pricesService, + workerService, + statisticService, + TimeSpan.FromHours(2), + WorkerType.PriceFourHour, + Timeframe.FourHour + ) + { + } +} diff --git a/src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs b/src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs new file mode 100644 index 0000000..7de871a --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs @@ -0,0 +1,23 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PricesOneDayWorker : PricesBaseWorker +{ + public PricesOneDayWorker( + ILogger logger, + IPricesService pricesService, + IStatisticService statisticService, + IWorkerService workerService) : base( + logger, + pricesService, + workerService, + statisticService, + TimeSpan.FromHours(12), + WorkerType.PriceOneDay, + Timeframe.OneDay + ) + { + } +} diff --git a/src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs b/src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs new file mode 100644 index 0000000..ce0b537 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs @@ -0,0 +1,22 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class PricesOneHourWorker : PricesBaseWorker +{ + public PricesOneHourWorker( + ILogger logger, + IPricesService pricesService, + IStatisticService statisticService, + IWorkerService workerService) : base( + logger, + pricesService, + workerService, + statisticService, + TimeSpan.FromMinutes(30), + WorkerType.PriceOneHour, + Timeframe.OneHour) + { + } +} diff --git a/src/Managing.Api.Workers/Workers/SpotlightWorker.cs b/src/Managing.Api.Workers/Workers/SpotlightWorker.cs new file mode 100644 index 0000000..91e550e --- /dev/null +++ b/src/Managing.Api.Workers/Workers/SpotlightWorker.cs @@ -0,0 +1,34 @@ +using Managing.Application.Workers.Abstractions; +using Managing.Common; + +namespace Managing.Api.Workers.Workers; + +public class SpotlightWorker : BaseWorker +{ + private readonly IStatisticService _statisticService; + + public SpotlightWorker( + ILogger logger, + IWorkerService workerService, + IStatisticService statisticService) : base( + Enums.WorkerType.Spotlight, + logger, + TimeSpan.FromMinutes(5), + workerService) + { + _statisticService = statisticService; + } + + protected async override Task Run(CancellationToken cancellationToken) + { + try + { + await _statisticService.UpdateSpotlight(); + } + catch (Exception ex) + { + _logger.LogError("Enable to update spotlight", ex); + throw; + } + } +} diff --git a/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs b/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs new file mode 100644 index 0000000..e103728 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs @@ -0,0 +1,28 @@ +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class TopVolumeTickerWorker : BaseWorker +{ + private readonly IStatisticService _statisticService; + private static readonly WorkerType _workerType = WorkerType.TopVolumeTicker; + + public TopVolumeTickerWorker( + ILogger logger, + IWorkerService workerService, + IStatisticService statisticService) : base( + _workerType, + logger, + TimeSpan.FromHours(12), + workerService + ) + { + _statisticService = statisticService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + await _statisticService.UpdateTopVolumeTicker(TradingExchanges.Evm, 10); + } +} diff --git a/src/Managing.Api.Workers/Workers/TraderWatcher.cs b/src/Managing.Api.Workers/Workers/TraderWatcher.cs new file mode 100644 index 0000000..90895a5 --- /dev/null +++ b/src/Managing.Api.Workers/Workers/TraderWatcher.cs @@ -0,0 +1,29 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Workers.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers.Workers; + +public class TraderWatcher : BaseWorker +{ + private readonly ITradingService _tradingService; + private static readonly WorkerType _workerType = WorkerType.TraderWatcher; + + public TraderWatcher( + ILogger logger, + ITradingService tradingService, + IWorkerService workerService) : base( + _workerType, + logger, + TimeSpan.FromSeconds(120), + workerService + ) + { + _tradingService = tradingService; + } + + protected override async Task Run(CancellationToken cancellationToken) + { + await _tradingService.WatchTrader(); + } +} diff --git a/src/Managing.Api.Workers/appsettings.Development.json b/src/Managing.Api.Workers/appsettings.Development.json new file mode 100644 index 0000000..b273bce --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.Development.json @@ -0,0 +1,23 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://localhost:8086/", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://localhost:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api.Workers/appsettings.Khalid.json b/src/Managing.Api.Workers/appsettings.Khalid.json new file mode 100644 index 0000000..d9754cb --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.Khalid.json @@ -0,0 +1,24 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ManagingDb", + }, + "InfluxDb": { + "Url": "http://localhost:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://localhost:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api.Workers/appsettings.Lowpro.json b/src/Managing.Api.Workers/appsettings.Lowpro.json new file mode 100644 index 0000000..72246f9 --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.Lowpro.json @@ -0,0 +1,39 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086", + "Token": "OPjdwQBmKr0zQecJ10IDQ4bt32oOJzmFp687QWWzbGeyH0R-gCA6HnXI_B0oQ_InPmSUXKFje8DSAUPbY0hn-w==", + "Organization": "managing-org" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "966075382002516031", + "PublicKey": "63028f6bb740cd5d26ae0340b582dee2075624011b28757436255fc002ca8a7c", + "TokenId": "OTY2MDc1MzgyMDAyNTE2MDMx.Yl8dzw.xpeIAaMwGrwTNY4r9JYv0ebzb-U", + + "SignalChannelId": 1134858150667898910, + "TradesChannelId": 1134858092530634864, + "TroublesChannelId": 1134858233031446671, + "CopyTradingChannelId": 1134857874896588881, + "RequestsChannelId": 1018589494968078356, + "LeaderboardChannelId": 1133169725237633095, + "NoobiesboardChannelId": 1133504653485690940, + "ButtonExpirationMinutes": 10 + + }, + "AllowedHosts": "*" +} diff --git a/src/Managing.Api.Workers/appsettings.Oda-docker.json b/src/Managing.Api.Workers/appsettings.Oda-docker.json new file mode 100644 index 0000000..e81d760 --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.Oda-docker.json @@ -0,0 +1,24 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api.Workers/appsettings.Oda.json b/src/Managing.Api.Workers/appsettings.Oda.json new file mode 100644 index 0000000..d9754cb --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.Oda.json @@ -0,0 +1,24 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ManagingDb", + }, + "InfluxDb": { + "Url": "http://localhost:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://localhost:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/Authorization/JwtMiddleware.cs b/src/Managing.Api/Authorization/JwtMiddleware.cs new file mode 100644 index 0000000..cc01bfd --- /dev/null +++ b/src/Managing.Api/Authorization/JwtMiddleware.cs @@ -0,0 +1,27 @@ +using Managing.Application.Abstractions.Services; + +namespace Managing.Api.Authorization; + + +public class JwtMiddleware +{ + private readonly RequestDelegate _next; + + public JwtMiddleware(RequestDelegate next, IConfiguration config) + { + _next = next; + } + + public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils) + { + var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); + var userId = jwtUtils.ValidateJwtToken(token); + if (userId != null) + { + // attach user to context on successful jwt validation + context.Items["User"] = await userService.GetUserByAddressAsync(userId); + } + + await _next(context); + } +} \ No newline at end of file diff --git a/src/Managing.Api/Authorization/JwtUtils.cs b/src/Managing.Api/Authorization/JwtUtils.cs new file mode 100644 index 0000000..ad60090 --- /dev/null +++ b/src/Managing.Api/Authorization/JwtUtils.cs @@ -0,0 +1,70 @@ +using Managing.Domain.Users; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace Managing.Api.Authorization; + + +public interface IJwtUtils +{ + public string GenerateJwtToken(User user, string publicAddress); + public string ValidateJwtToken(string token); +} + +public class JwtUtils : IJwtUtils +{ + private readonly string _secret; + public JwtUtils(IConfiguration config) + { + _secret = config.GetValue("Jwt:Secret"); + } + + public string GenerateJwtToken(User user, string publicAddress) + { + // generate token that is valid for 15 minutes + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_secret); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] { new Claim("address", publicAddress) }), + Expires = DateTime.UtcNow.AddDays(15), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public string ValidateJwtToken(string token) + { + if (token == null || string.IsNullOrEmpty(token)) + return null; + + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_secret); + try + { + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + var address = jwtToken.Claims.First(x => x.Type == "address").Value; + + // return user id from JWT token if validation successful + return address; + } + catch + { + // return null if validation fails + return null; + } + } +} diff --git a/src/Managing.Api/Controllers/AccountController.cs b/src/Managing.Api/Controllers/AccountController.cs new file mode 100644 index 0000000..845355e --- /dev/null +++ b/src/Managing.Api/Controllers/AccountController.cs @@ -0,0 +1,58 @@ +using Managing.Application.Abstractions.Services; +using Managing.Domain.Accounts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Managing.Api.Controllers +{ + [Authorize] + public class AccountController : BaseController + { + private readonly IAccountService _AccountService; + + public AccountController( + IAccountService AccountService, + IUserService userService) + : base(userService) + { + _AccountService = AccountService; + } + + [HttpPost] + public async Task> PostAccount(Account Account) + { + var user = await GetUser(); + return Ok(await _AccountService.CreateAccount(user, Account)); + } + + [HttpGet] + [Route("accounts")] + public async Task>> GetAccounts() + { + var user = await GetUser(); + return Ok(_AccountService.GetAccountsByUser(user, true)); + } + + [HttpGet] + [Route("balances")] + public async Task>> GetAccountsBalances() + { + var user = await GetUser(); + return Ok(_AccountService.GetAccountsBalancesByUser(user)); + } + + [HttpGet] + public async Task> GetAccount(string name) + { + var user = await GetUser(); + return Ok(await _AccountService.GetAccountByUser(user, name, true, true)); + } + + [HttpDelete] + public ActionResult DeleteAccount(string name) + { + var user = GetUser().Result; + return Ok(_AccountService.DeleteAccount(user, name)); + } + } +} diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs new file mode 100644 index 0000000..22558e7 --- /dev/null +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -0,0 +1,133 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Hubs; +using Managing.Domain.Backtests; +using Managing.Domain.MoneyManagements; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using static Managing.Common.Enums; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +[Produces("application/json")] +public class BacktestController : ControllerBase +{ + private readonly IHubContext _hubContext; + private readonly IBacktester _backtester; + private readonly IScenarioService _scenarioService; + private readonly IAccountService _accountService; + private readonly IMoneyManagementService _moneyManagementService; + + public BacktestController( + IHubContext hubContext, + IBacktester backtester, + IScenarioService scenarioService, + IAccountService accountService, + IMoneyManagementService moneyManagementService) + { + _hubContext = hubContext; + _backtester = backtester; + _scenarioService = scenarioService; + _accountService = accountService; + _moneyManagementService = moneyManagementService; + } + + [HttpGet] + public ActionResult> Backtests() + { + return Ok(_backtester.GetBacktests()); + } + + [HttpDelete] + public ActionResult DeleteBacktest(string id) + { + return Ok(_backtester.DeleteBacktest(id)); + } + + [HttpDelete] + [Route("deleteAll")] + public ActionResult DeleteBacktests() + { + return Ok(_backtester.DeleteBacktests()); + } + + [HttpPost] + [Route("Run")] + public async Task> Run(string accountName, + BotType botType, + Ticker ticker, + string scenarioName, + Timeframe timeframe, + bool watchOnly, + int days, + decimal balance, + string moneyManagementName, + MoneyManagement? moneyManagement = null, + bool save = false) + { + if (string.IsNullOrEmpty(accountName)) + { + throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName)); + } + + if (string.IsNullOrEmpty(scenarioName)) + { + throw new ArgumentException($"'{nameof(scenarioName)}' cannot be null or empty.", nameof(scenarioName)); + } + + if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null) + { + throw new ArgumentException($"'{nameof(moneyManagementName)}' and '{nameof(moneyManagement)}' cannot be null or empty.", nameof(moneyManagementName)); + } + + if (days > 0) + { + days = days * -1; + } + + Backtest backtestResult = null; + var scenario = _scenarioService.GetScenario(scenarioName); + var account = await _accountService.GetAccount(accountName, true, false); + + if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null) + { + moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName); + } + else + { + moneyManagement.FormatPercentage(); + } + + if (scenario == null) + return BadRequest("No scenario found"); + + switch (botType) + { + case BotType.SimpleBot: + break; + case BotType.ScalpingBot: + backtestResult = _backtester.RunScalpingBotBacktest(account, moneyManagement, ticker, scenario, + timeframe, Convert.ToDouble(days), balance, watchOnly, save); + break; + case BotType.FlippingBot: + backtestResult = _backtester.RunFlippingBotBacktest(account, moneyManagement, ticker, scenario, + timeframe, Convert.ToDouble(days), balance, watchOnly, save); + break; + } + + await NotifyBacktesingSubscriberAsync(backtestResult); + + return Ok(backtestResult); + } + + private async Task NotifyBacktesingSubscriberAsync(Backtest backtesting) + { + if(backtesting != null){ + await _hubContext.Clients.All.SendAsync("BacktestsSubscription", backtesting); + } + } +} diff --git a/src/Managing.Api/Controllers/BaseController.cs b/src/Managing.Api/Controllers/BaseController.cs new file mode 100644 index 0000000..70c6fdc --- /dev/null +++ b/src/Managing.Api/Controllers/BaseController.cs @@ -0,0 +1,35 @@ +using Managing.Application.Abstractions.Services; +using Managing.Domain.Users; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace Managing.Api.Controllers; + +[ApiController] +[Route("[controller]")] +[Produces("application/json")] +public abstract class BaseController : ControllerBase +{ + private readonly IUserService _userService; + + public BaseController(IUserService userService) + { + _userService = userService; + } + + protected async Task GetUser() + { + var identity = HttpContext?.User.Identity as ClaimsIdentity; + if (identity != null) + { + var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value; + var user = await _userService.GetUserByAddressAsync(address); + + if (user != null) + return user; + + throw new Exception("User not found for this token"); + } + throw new Exception("Not identity assigned to this token"); + } +} diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs new file mode 100644 index 0000000..7ef588e --- /dev/null +++ b/src/Managing.Api/Controllers/BotController.cs @@ -0,0 +1,168 @@ +using Managing.Api.Models.Requests; +using Managing.Api.Models.Responses; +using Managing.Application.Abstractions; +using Managing.Application.Hubs; +using Managing.Application.ManageBot.Commands; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using static Managing.Common.Enums; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +[Produces("application/json")] +public class BotController : ControllerBase +{ + private readonly IMediator _mediator; + private readonly ILogger _logger; + private readonly IHubContext _hubContext; + private readonly IBacktester _backtester; + + public BotController(ILogger logger, IMediator mediator, IHubContext hubContext, IBacktester backtester) + { + _logger = logger; + _mediator = mediator; + _hubContext = hubContext; + _backtester = backtester; + } + + [HttpPost] + [Route("Start")] + public async Task> 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)); + + await NotifyBotSubscriberAsync(); + return Ok(result); + } + + [HttpGet] + [Route("Stop")] + public async Task> Stop(BotType botType, string botName) + { + var result = await _mediator.Send(new StopBotCommand(botType, botName)); + _logger.LogInformation($"{botType} type called {botName} is now {result}"); + + await NotifyBotSubscriberAsync(); + + return Ok(result); + } + + [HttpDelete] + [Route("Delete")] + public async Task> Delete(string botName) + { + var result = await _mediator.Send(new DeleteBotCommand(botName)); + _logger.LogInformation($"{botName} is now deleted"); + + await NotifyBotSubscriberAsync(); + + return Ok(result); + } + + [HttpGet] + [Route("StopAll")] + public async Task StopAll() + { + var bots = await GetBotList(); + var result = ""; + foreach (var bot in bots) + { + result += $"{bot.Name} : "; + result = await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); + result += $" |"; + } + + await NotifyBotSubscriberAsync(); + + return result; + } + + [HttpGet] + [Route("Restart")] + public async Task> Restart(BotType botType, string botName) + { + var result = await _mediator.Send(new RestartBotCommand(botType, botName)); + _logger.LogInformation($"{botType} type called {botName} is now {result}"); + + await NotifyBotSubscriberAsync(); + + return Ok(result); + } + + [HttpGet] + [Route("RestartAll")] + public async Task RestartAll() + { + var bots = await GetBotList(); + var result = ""; + foreach (var bot in bots) + { + result += $"{bot.Name} : "; + result = await _mediator.Send(new RestartBotCommand(bot.BotType, bot.Name)); + result += $" |"; + } + + await NotifyBotSubscriberAsync(); + + return result; + } + + [HttpGet] + [Route("ToggleIsForWatching")] + public async Task> ToggleIsForWatching(string botName) + { + var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName)); + _logger.LogInformation($"{botName} bot is now {result}"); + + await NotifyBotSubscriberAsync(); + + return Ok(result); + } + + [HttpGet] + public async Task> GetActiveBots() + { + return await GetBotList(); + } + + private async Task> GetBotList() + { + var result = await _mediator.Send(new GetActiveBotsCommand()); + var list = new List(); + + foreach (var item in result) + { + list.Add(new TradingBot + { + Status = item.GetStatus(), + Name = item.GetName(), + Candles = item.Candles.ToList(), + Positions = item.Positions, + Signals = item.Signals.ToList(), + WinRate = item.GetWinRate(), + ProfitAndLoss = item.GetProfitAndLoss(), + Timeframe = item.Timeframe, + Ticker = item.Ticker, + AccountName = item.AccountName, + Scenario = item.Scenario, + IsForWatchingOnly = item.IsForWatchingOnly, + BotType = item.BotType, + MoneyManagement = item.MoneyManagement + }); + } + + return list; + } + + private async Task NotifyBotSubscriberAsync() + { + var botsList = await GetBotList(); + await _hubContext.Clients.All.SendAsync("BotsSubscription", botsList); + } +} diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs new file mode 100644 index 0000000..01f0398 --- /dev/null +++ b/src/Managing.Api/Controllers/DataController.cs @@ -0,0 +1,73 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Hubs; +using Managing.Application.Workers.Abstractions; +using Managing.Domain.Candles; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using static Managing.Common.Enums; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +public class DataController : ControllerBase +{ + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ICacheService _cacheService; + private readonly IStatisticService _statisticService; + private readonly IHubContext _hubContext; + + public DataController( + IExchangeService exchangeService, + IAccountService accountService, + ICacheService cacheService, + IStatisticService statisticService, + IHubContext hubContext) + { + _exchangeService = exchangeService; + _accountService = accountService; + _cacheService = cacheService; + _statisticService = statisticService; + _hubContext = hubContext; + } + + [HttpPost("GetTickers")] + public async Task> GetTickers(string accountName, Timeframe timeframe) + { + var account = await _accountService.GetAccount(accountName, true, false); + var cacheKey = string.Concat(accountName, timeframe.ToString()); + var tickers = _cacheService.GetOrSave(cacheKey, () => + { + return _exchangeService.GetTickers(account, timeframe).Result; + }, TimeSpan.FromHours(2)); + + return Ok(tickers); + } + + [HttpGet("Spotlight")] + public ActionResult GetSpotlight() + { + var overview = _cacheService.GetOrSave(nameof(SpotlightOverview), () => + { + return _statisticService.GetLastSpotlight(DateTime.Now.AddHours(-3)); + }, TimeSpan.FromMinutes(2)); + + if (overview?.Spotlights.Count < overview?.ScenarioCount) + { + overview = _statisticService.GetLastSpotlight(DateTime.Now.AddHours(-3)); + } + + return Ok(overview); + } + + [HttpGet("GetCandles")] + public async Task>> GetCandles(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + return Ok(await _exchangeService.GetCandlesInflux(exchange, ticker, startDate, timeframe)); + } + +} diff --git a/src/Managing.Api/Controllers/MoneyManagementController.cs b/src/Managing.Api/Controllers/MoneyManagementController.cs new file mode 100644 index 0000000..6ce38a9 --- /dev/null +++ b/src/Managing.Api/Controllers/MoneyManagementController.cs @@ -0,0 +1,44 @@ +using Managing.Application.Abstractions; +using Managing.Domain.MoneyManagements; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +[Produces("application/json")] +public class MoneyManagementController : ControllerBase +{ + private readonly IMoneyManagementService _moneyManagementService; + public MoneyManagementController(IMoneyManagementService moneyManagementService) + { + _moneyManagementService = moneyManagementService; + } + + [HttpPost] + public async Task> PostMoneyManagement(MoneyManagement moneyManagement) + { + return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement)); + } + + [HttpGet] + [Route("moneymanagements")] + public ActionResult> GetMoneyManagements() + { + return Ok(_moneyManagementService.GetMoneyMangements()); + } + + [HttpGet] + public ActionResult GetMoneyManagement(string name) + { + return Ok(_moneyManagementService.GetMoneyMangement(name)); + } + + [HttpDelete] + public ActionResult DeleteMoneyManagement(string name) + { + return Ok(_moneyManagementService.DeleteMoneyManagement(name)); + } +} diff --git a/src/Managing.Api/Controllers/ScenarioController.cs b/src/Managing.Api/Controllers/ScenarioController.cs new file mode 100644 index 0000000..2a55c9a --- /dev/null +++ b/src/Managing.Api/Controllers/ScenarioController.cs @@ -0,0 +1,83 @@ +using Managing.Application.Abstractions; +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using static Managing.Common.Enums; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +[Produces("application/json")] +public class ScenarioController : ControllerBase +{ + private readonly IScenarioService _scenarioService; + public ScenarioController(IScenarioService scenarioService) + { + _scenarioService = scenarioService; + } + + [HttpGet] + public ActionResult> GetScenarios() + { + return Ok(_scenarioService.GetScenarios()); + } + + [HttpPost] + public ActionResult CreateScenario(string name, List strategies) + { + return Ok(_scenarioService.CreateScenario(name, strategies)); + } + + + [HttpDelete] + public ActionResult DeleteScenario(string name) + { + return Ok(_scenarioService.DeleteScenario(name)); + } + + [HttpGet] + [Route("strategy")] + public ActionResult> GetStrategies() + { + return Ok(_scenarioService.GetStrategies()); + } + + [HttpPost] + [Route("strategy")] + public ActionResult CreateStrategy( + StrategyType strategyType, + Timeframe timeframe, + 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) + { + return Ok(_scenarioService.CreateStrategy( + strategyType, + timeframe, + name, + period, + fastPeriods, + slowPeriods, + signalPeriods, + multiplier, + stochPeriods, + smoothPeriods, + cyclePeriods)); + } + + [HttpDelete] + [Route("strategy")] + public ActionResult DeleteStrategy(string name) + { + return Ok(_scenarioService.DeleteStrategy(name)); + } +} diff --git a/src/Managing.Api/Controllers/SettingsController.cs b/src/Managing.Api/Controllers/SettingsController.cs new file mode 100644 index 0000000..f987063 --- /dev/null +++ b/src/Managing.Api/Controllers/SettingsController.cs @@ -0,0 +1,30 @@ +using Managing.Application.Abstractions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +[Produces("application/json")] +public class SettingsController : ControllerBase +{ + private readonly ISettingsService _settingsService; + public SettingsController(ISettingsService settingsService) + { + _settingsService = settingsService; + } + + [HttpPost] + public ActionResult SetupSettings() + { + return Ok(_settingsService.SetupSettings()); + } + + [HttpDelete] + public async Task> ResetSettings() + { + return Ok(await _settingsService.ResetSettings()); + } +} diff --git a/src/Managing.Api/Controllers/TradingController.cs b/src/Managing.Api/Controllers/TradingController.cs new file mode 100644 index 0000000..bec3be2 --- /dev/null +++ b/src/Managing.Api/Controllers/TradingController.cs @@ -0,0 +1,107 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Trades; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using static Managing.Common.Enums; + +namespace Managing.Api.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +public class TradingController : ControllerBase +{ + private readonly ICommandHandler _openTradeCommandHandler; + private readonly ICommandHandler _closeTradeCommandHandler; + private readonly ITradingService _tradingService; + private readonly IMoneyManagementService _moneyManagementService; + private readonly IMediator _mediator; + + private readonly ILogger _logger; + + public TradingController( + ILogger logger, + ICommandHandler openTradeCommandHandler, + ICommandHandler closeTradeCommandHandler, + ITradingService tradingService, + IMediator mediator) + { + _logger = logger; + _openTradeCommandHandler = openTradeCommandHandler; + _closeTradeCommandHandler = closeTradeCommandHandler; + _tradingService = tradingService; + _mediator = mediator; + } + + [HttpGet("GetPositions")] + public async Task>> GetPositions(PositionInitiator positionInitiator) + { + var result = await _mediator.Send(new GetPositionsCommand(positionInitiator)); + return Ok(result); + } + + [HttpGet("GetTrade")] + public async Task> GetTrade(string accountName, Ticker ticker, string exchangeOrderId) + { + var result = await _mediator.Send(new GetTradeCommand(accountName, exchangeOrderId, ticker)); + return Ok(result); + } + + [HttpGet("GetTrades")] + public async Task> GetTrades(string accountName, Ticker ticker, string exchangeOrderId) + { + var result = await _mediator.Send(new GetTradesCommand(ticker, accountName)); + return Ok(result); + } + + [HttpGet("ClosePosition")] + public async Task> ClosePosition(string identifier) + { + var position = _tradingService.GetPositionByIdentifier(identifier); + var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position)); + return Ok(result); + } + + [HttpGet("OpenPosition")] + public async Task> Trade( + string accountName, + string moneyManagementName, + TradeDirection direction, + Ticker ticker, + RiskLevel riskLevel, + bool isForPaperTrading, + MoneyManagement? moneyManagement = null, + decimal? openPrice = null) + { + if (string.IsNullOrEmpty(accountName)) + { + throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName)); + } + + if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null) + { + throw new ArgumentException($"'{nameof(moneyManagementName)}' cannot be null or empty.", nameof(moneyManagementName)); + } + + if (moneyManagement == null) + { + moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName); + } + + var command = new OpenPositionRequest( + accountName, + moneyManagement, + direction, + ticker, + PositionInitiator.User, + DateTime.UtcNow, + isForPaperTrading: isForPaperTrading, + price: openPrice); + var result = await _openTradeCommandHandler.Handle(command); + return Ok(result); + } +} diff --git a/src/Managing.Api/Controllers/UserController.cs b/src/Managing.Api/Controllers/UserController.cs new file mode 100644 index 0000000..0ec0c71 --- /dev/null +++ b/src/Managing.Api/Controllers/UserController.cs @@ -0,0 +1,39 @@ +using Managing.Api.Authorization; +using Managing.Api.Models.Requests; +using Managing.Application.Abstractions.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Managing.Api.Controllers; + +[ApiController] +[Route("[controller]")] +[Produces("application/json")] +public class UserController : ControllerBase +{ + private IConfiguration _config; + private readonly IUserService _userService; + private readonly IJwtUtils _jwtUtils; + + public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils) + { + _config = config; + _userService = userService; + _jwtUtils = jwtUtils; + } + + [AllowAnonymous] + [HttpPost] + public async Task> CreateToken([FromBody] LoginRequest login) + { + var user = await _userService.Authenticate(login.Name, login.Address, login.Message, login.Signature); + + if (user != null) + { + var tokenString = _jwtUtils.GenerateJwtToken(user, login.Address); + return Ok(tokenString); + } + + return Unauthorized(); + } +} diff --git a/src/Managing.Api/Controllers/WorkflowController.cs b/src/Managing.Api/Controllers/WorkflowController.cs new file mode 100644 index 0000000..2e33a60 --- /dev/null +++ b/src/Managing.Api/Controllers/WorkflowController.cs @@ -0,0 +1,45 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Domain.Workflows; +using Managing.Domain.Workflows.Synthetics; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Managing.Api.Controllers +{ + [Authorize] + public class WorkflowController : BaseController + { + private readonly IWorkflowService _workflowService; + + public WorkflowController(IWorkflowService WorkflowService, IUserService userService) : base(userService) + { + _workflowService = WorkflowService; + } + + [HttpPost] + public async Task> PostWorkflow([ModelBinder]SyntheticWorkflow workflowRequest) + { + return Ok(await _workflowService.InsertOrUpdateWorkflow(workflowRequest)); + } + + [HttpGet] + public ActionResult> GetWorkflows() + { + return Ok(_workflowService.GetWorkflows()); + } + + [HttpGet] + [Route("flows")] + public async Task>> GetAvailableFlows() + { + return Ok(await _workflowService.GetAvailableFlows()); + } + + [HttpDelete] + public ActionResult DeleteWorkflow(string name) + { + return Ok(_workflowService.DeleteWorkflow(name)); + } + } +} diff --git a/src/Managing.Api/Dockerfile b/src/Managing.Api/Dockerfile new file mode 100644 index 0000000..484044a --- /dev/null +++ b/src/Managing.Api/Dockerfile @@ -0,0 +1,36 @@ +# Use the official Microsoft ASP.NET Core runtime as the base image. +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +# Use the official Microsoft .NET SDK image to build the code. +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /src +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/"] +COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"] +COPY ["Managing.Application.Workers/Managing.Application.Workers.csproj", "Managing.Application.Workers/"] +COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"] +COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"] +COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"] +RUN dotnet restore "Managing.Api/Managing.Api.csproj" +COPY . . +WORKDIR "/src/Managing.Api" +RUN dotnet build "Managing.Api.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Managing.Api.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +COPY Managing.Api/managing_cert.pfx . +COPY Managing.Api/appsettings.Lowpro.json . +ENTRYPOINT ["dotnet", "Managing.Api.dll"] diff --git a/src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs b/src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs new file mode 100644 index 0000000..84308bb --- /dev/null +++ b/src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs @@ -0,0 +1,62 @@ +using System.Net; +using System.Text.Json; + +namespace Managing.Api.Exceptions; + +public class GlobalErrorHandlingMiddleware +{ + private readonly RequestDelegate _next; + public GlobalErrorHandlingMiddleware(RequestDelegate next) + { + _next = next; + } + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + private static Task HandleExceptionAsync(HttpContext context, Exception exception) + { + HttpStatusCode status; + var exceptionType = exception.GetType(); + + if (exceptionType == typeof(Exception)) + { + status = HttpStatusCode.InternalServerError; + } + else if (exceptionType == typeof(NotImplementedException)) + { + status = HttpStatusCode.NotImplemented; + } + else if (exceptionType == typeof(UnauthorizedAccessException)) + { + status = HttpStatusCode.Unauthorized; + } + else if (exceptionType == typeof(ArgumentException)) + { + status = HttpStatusCode.Unauthorized; + } + else if (exceptionType == typeof(KeyNotFoundException)) + { + status = HttpStatusCode.Unauthorized; + } + else + { + status = HttpStatusCode.InternalServerError; + } + + var message = exception.Message; + var stackTrace = exception.StackTrace; + var exceptionResult = JsonSerializer.Serialize(new { error = message, stackTrace }); + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)status; + return context.Response.WriteAsync(exceptionResult); + } +} diff --git a/src/Managing.Api/Filters/EnumSchemaFilter.cs b/src/Managing.Api/Filters/EnumSchemaFilter.cs new file mode 100644 index 0000000..7cc60ec --- /dev/null +++ b/src/Managing.Api/Filters/EnumSchemaFilter.cs @@ -0,0 +1,20 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Managing.Api.Filters +{ + public class EnumSchemaFilter : ISchemaFilter + { + public void Apply(OpenApiSchema model, SchemaFilterContext context) + { + if (context.Type.IsEnum) + { + model.Enum.Clear(); + Enum.GetNames(context.Type) + .ToList() + .ForEach(n => model.Enum.Add(new OpenApiString(n))); + } + } + } +} diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj new file mode 100644 index 0000000..4b1e176 --- /dev/null +++ b/src/Managing.Api/Managing.Api.csproj @@ -0,0 +1,43 @@ + + + + net7.0 + enable + AnyCPU;x64 + ..\..\docker-compose.dcproj + 7476db9f-ade4-414a-a420-e3ab55cb5f8d + Linux + ..\.. + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + diff --git a/src/Managing.Api/Models/Requests/CreateScenarioRequest.cs b/src/Managing.Api/Models/Requests/CreateScenarioRequest.cs new file mode 100644 index 0000000..95ed55d --- /dev/null +++ b/src/Managing.Api/Models/Requests/CreateScenarioRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Managing.Api.Models.Requests +{ + public class CreateScenarioRequest + { + [Required] + public string Name { get; internal set; } + [Required] + public List Strategies { get; internal set; } + } +} diff --git a/src/Managing.Api/Models/Requests/CreateStrategyRequest.cs b/src/Managing.Api/Models/Requests/CreateStrategyRequest.cs new file mode 100644 index 0000000..88fc80f --- /dev/null +++ b/src/Managing.Api/Models/Requests/CreateStrategyRequest.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Api.Models.Requests; + +public class CreateStrategyRequest +{ + [Required] + public StrategyType Type { get; internal set; } + [Required] + public Timeframe Timeframe { get; internal set; } + [Required] + public string Name { get; internal set; } + [Required] + public int Period { get; internal set; } +} diff --git a/src/Managing.Api/Models/Requests/LoginRequest.cs b/src/Managing.Api/Models/Requests/LoginRequest.cs new file mode 100644 index 0000000..bfeea19 --- /dev/null +++ b/src/Managing.Api/Models/Requests/LoginRequest.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Managing.Api.Models.Requests; + +public class LoginRequest +{ + [Required] + public string Name { get; set; } + [Required] + public string Address { get; set; } + [Required] + public string Signature { get; set; } + [Required] + public string Message { get; set; } +} diff --git a/src/Managing.Api/Models/Requests/RunBacktestRequest.cs b/src/Managing.Api/Models/Requests/RunBacktestRequest.cs new file mode 100644 index 0000000..f9b6cb3 --- /dev/null +++ b/src/Managing.Api/Models/Requests/RunBacktestRequest.cs @@ -0,0 +1,15 @@ +using static Managing.Common.Enums; + +namespace Managing.Api.Models.Requests +{ + public class RunBacktestRequest + { + public TradingExchanges Exchange { get; set; } + public BotType BotType { get; set; } + public Ticker Ticker { get; set; } + public Timeframe Timeframe { get; set; } + public RiskLevel RiskLevel { get; set; } + public bool WatchOnly { get; set; } + public int Days { get; set; } + } +} diff --git a/src/Managing.Api/Models/Requests/StartBotRequest.cs b/src/Managing.Api/Models/Requests/StartBotRequest.cs new file mode 100644 index 0000000..a477733 --- /dev/null +++ b/src/Managing.Api/Models/Requests/StartBotRequest.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Api.Models.Requests +{ + public class StartBotRequest + { + [Required] + public BotType BotType { get; set; } + [Required] + public string BotName { get; set; } + [Required] + public Ticker Ticker { get; set; } + [Required] + public Timeframe Timeframe { get; set; } + [Required] + public bool IsForWatchOnly { get; set; } + [Required] + public string Scenario { get; set; } + [Required] + public string AccountName { get; set; } + [Required] + public string MoneyManagementName { get; set; } + } +} diff --git a/src/Managing.Api/Models/Responses/TradingBot.cs b/src/Managing.Api/Models/Responses/TradingBot.cs new file mode 100644 index 0000000..e49bff9 --- /dev/null +++ b/src/Managing.Api/Models/Responses/TradingBot.cs @@ -0,0 +1,45 @@ +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +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 Signals { get; internal set; } + [Required] + public List Positions { get; internal set; } + [Required] + public List 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; } + } +} diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs new file mode 100644 index 0000000..ef4d5e7 --- /dev/null +++ b/src/Managing.Api/Program.cs @@ -0,0 +1,168 @@ +using System.Text; +using System.Text.Json.Serialization; +using Managing.Api.Authorization; +using Managing.Api.Exceptions; +using Managing.Api.Filters; +using Managing.Application.Hubs; +using Managing.Bootstrap; +using Managing.Common; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Managing.Infrastructure.Databases.MongoDb; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using NSwag; +using NSwag.Generation.Processors.Security; +using Serilog; +using Serilog.Sinks.Elasticsearch; + +// Builder +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddJsonFile("appsettings.Lowpro.json", optional: true, reloadOnChange: true) + .AddJsonFile($"config.{builder.Environment.EnvironmentName}.json", + optional: true, reloadOnChange: true); + +builder.Configuration.AddEnvironmentVariables(); +builder.Configuration.AddUserSecrets(); +builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => +{ + var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-"); + var indexFormat = $"managing-{envName}-" + "{0:yyyy.MM.dd}"; + var yourTemplateName = "dotnetlogs"; + var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"])) + { + IndexFormat = indexFormat.ToLower(), + AutoRegisterTemplate = true, + OverwriteTemplate = true, + TemplateName = yourTemplateName, + AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, + TypeName = null, + BatchAction = ElasticOpType.Create, + MinimumLogEventLevel = Serilog.Events.LogEventLevel.Information, + FailureCallback = e => Console.WriteLine($"Unable to submit event {e.RenderMessage()} to ElasticSearch. " + + $"Full message : " + e.Exception.Message), + DetectElasticsearchVersion = true, + RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, + }; + + loggerConfiguration + .WriteTo.Console() + .WriteTo.Elasticsearch(es); +}); + +builder.Services.AddOptions(); +builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.MongoDb)); +builder.Services.Configure(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); +builder.Services.AddControllers().AddJsonOptions(options => + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o => +{ + o.SaveToken = true; + o.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = builder.Configuration["Authentication:Schemes:Bearer:ValidIssuer"], + ValidAudience = builder.Configuration["Authentication:Schemes:Bearer:ValidAudiences"], + IssuerSigningKey = new SymmetricSecurityKey + (Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"])), + ValidateIssuer = false, + ValidateAudience = false, + ValidateIssuerSigningKey = true + }; +}); +builder.Services.AddAuthorization(); +builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder => +{ + builder + .SetIsOriginAllowed((host) => true) + .AllowAnyOrigin() + .WithOrigins("http://localhost:3000/") + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); +})); + +builder.Services.AddSignalR().AddJsonProtocol(); +builder.Services.AddScoped(); + +builder.Services.RegisterApiDependencies(builder.Configuration); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddOpenApiDocument(document => +{ + document.AddSecurity("JWT", Enumerable.Empty(), new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.ApiKey, + Name = "Authorization", + In = OpenApiSecurityApiKeyLocation.Header, + Description = "Type into the textbox: Bearer {your JWT token}." + }); + + document.OperationProcessors.Add( + new AspNetCoreOperationSecurityScopeProcessor("JWT")); +}); +builder.Services.AddSwaggerGen(options => +{ + options.SchemaFilter(); + options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme + { + Description = "Please insert your JWT Token into field : Bearer {your_token}", + Name = "Authorization", + Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http, + In = Microsoft.OpenApi.Models.ParameterLocation.Header, + Scheme = "Bearer", + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement{ + { + new Microsoft.OpenApi.Models.OpenApiSecurityScheme{ + Reference = new Microsoft.OpenApi.Models.OpenApiReference{ + Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[]{} + } + }); +}); + +builder.WebHost.SetupDiscordBot(); + +// App +var app = builder.Build(); +app.UseSerilogRequestLogging(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseOpenApi(); +app.UseSwaggerUi3(); +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing API v1"); + c.RoutePrefix = string.Empty; +}); + +app.UseCors("CorsPolicy"); + +app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware)); + +app.UseMiddleware(); + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseAuthentication(); + +app.UseAuthorization(); + +app.UseEndpoints(endpoints => +{ + endpoints.MapControllers(); + endpoints.MapHub("/bothub"); + endpoints.MapHub("/backtesthub"); + endpoints.MapHub("/candlehub"); +}); + +app.Run(); diff --git a/src/Managing.Api/appsettings.Development.json b/src/Managing.Api/appsettings.Development.json new file mode 100644 index 0000000..c566931 --- /dev/null +++ b/src/Managing.Api/appsettings.Development.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Error", + "System": "Error", + "Microsoft": "Warning" + } + }, + "AllowedHosts": "*", + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200/" + } +} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Khalid.json b/src/Managing.Api/appsettings.Khalid.json new file mode 100644 index 0000000..46bd192 --- /dev/null +++ b/src/Managing.Api/appsettings.Khalid.json @@ -0,0 +1,34 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://localhost:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "", + "PublicKey": "", + "SignalChannelId": 1018897743118340180, + "TroublesChannelId": 1018897743118340180, + "TradesChannelId": 1020457417877753886, + "RequestChannelId": 1020463151034138694, + "RequestsChannelId": 1020463151034138694, + "ButtonExpirationMinutes": 2 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Lowpro.json b/src/Managing.Api/appsettings.Lowpro.json new file mode 100644 index 0000000..a59bfc6 --- /dev/null +++ b/src/Managing.Api/appsettings.Lowpro.json @@ -0,0 +1,46 @@ +{ + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ "http://localhost:3000/" ], + "ValidIssuer": "Managing" + } + } + }, + "Jwt": { + "Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6" + }, + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "managing-org", + "Token": "OPjdwQBmKr0zQecJ10IDQ4bt32oOJzmFp687QWWzbGeyH0R-gCA6HnXI_B0oQ_InPmSUXKFje8DSAUPbY0hn-w==" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "966075382002516031", + "PublicKey": "63028f6bb740cd5d26ae0340b582dee2075624011b28757436255fc002ca8a7c", + "TokenId": "OTY2MDc1MzgyMDAyNTE2MDMx.Yl8dzw.xpeIAaMwGrwTNY4r9JYv0ebzb-U", + "SignalChannelId": 1134858150667898910, + "TradesChannelId": 1134858092530634864, + "TroublesChannelId": 1134858233031446671, + "CopyTradingChannelId": 1134857874896588881, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 10 + }, + "AllowedHosts": "*" +} diff --git a/src/Managing.Api/appsettings.Oda-Sandbox.json b/src/Managing.Api/appsettings.Oda-Sandbox.json new file mode 100644 index 0000000..c8c284b --- /dev/null +++ b/src/Managing.Api/appsettings.Oda-Sandbox.json @@ -0,0 +1,37 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "BotActivity": "trading strategies", + "HandleUserAction": true, + "ApplicationId": "", + "PublicKey": "", + "TokenId": "", + "SignalChannelId": 966080506473099314, + "TradesChannelId": 998374177763491851, + "TroublesChannelId": 1015761955321040917, + "CopyTradingChannelId": 1132022887012909126, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 10 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Oda-docker.json b/src/Managing.Api/appsettings.Oda-docker.json new file mode 100644 index 0000000..e59c1c4 --- /dev/null +++ b/src/Managing.Api/appsettings.Oda-docker.json @@ -0,0 +1,36 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb", + "UserName": "admin", + "Password": "!MotdepasseFort11" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "managing-org", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "", + "PublicKey": "", + "TokenId": "", + "SignalChannelId": 966080506473099314, + "TradesChannelId": 998374177763491851, + "TroublesChannelId": 1015761955321040917, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 2 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.Oda.json b/src/Managing.Api/appsettings.Oda.json new file mode 100644 index 0000000..e9b2a8d --- /dev/null +++ b/src/Managing.Api/appsettings.Oda.json @@ -0,0 +1,34 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://localhost:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "", + "PublicKey": "", + "TokenId": "", + "SignalChannelId": 966080506473099314, + "TradesChannelId": 998374177763491851, + "TroublesChannelId": 1015761955321040917, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 2 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/appsettings.json b/src/Managing.Api/appsettings.json new file mode 100644 index 0000000..9a4f9a0 --- /dev/null +++ b/src/Managing.Api/appsettings.json @@ -0,0 +1,48 @@ +{ + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ "http://localhost:3000/" ], + "ValidIssuer": "Managing" + } + } + }, + "Jwt": { + "Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6" + }, + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200/" + }, + "Discord": { + "BotActivity": "trading strategies", + "HandleUserAction": true, + "ApplicationId": "", + "PublicKey": "", + "TokenId": "", + "SignalChannelId": 966080506473099314, + "TradesChannelId": 998374177763491851, + "TroublesChannelId": 1015761955321040917, + "CopyTradingChannelId": 1132022887012909126, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 10 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/captain-definition b/src/Managing.Api/captain-definition new file mode 100644 index 0000000..61c2e42 --- /dev/null +++ b/src/Managing.Api/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "dockerfilePath": "Dockerfile" +} diff --git a/src/Managing.Api/failures.txt b/src/Managing.Api/failures.txt new file mode 100644 index 0000000..d4d21d2 --- /dev/null +++ b/src/Managing.Api/failures.txt @@ -0,0 +1,19 @@ +{"Timestamp":"2022-09-13T02:59:02.0040536+02:00","Level":"Information","MessageTemplate":"02:59:01 Discord Discord.Net v3.8.0 (API v9)","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.0892523+02:00","Level":"Information","MessageTemplate":"02:59:02 Gateway Connecting","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.2504922+02:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"https://localhost:5001","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.2556804+02:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"http://localhost:5000","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.2591276+02:00","Level":"Information","MessageTemplate":"Application started. Press Ctrl+C to shut down.","Properties":{"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.2625140+02:00","Level":"Information","MessageTemplate":"Hosting environment: {envName}","Properties":{"envName":"Development","SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:02.2651704+02:00","Level":"Information","MessageTemplate":"Content root path: {contentRoot}","Properties":{"contentRoot":"C:\\Users\\Utilisateur\\Desktop\\Projects\\apps\\Managing\\src\\Managing.Api\\","SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:03.3572754+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway You're using the GuildScheduledEvents gateway intent without listening to any events related to that intent, consider removing the intent from your config.","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:03.3628433+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway You're using the GuildInvites gateway intent without listening to any events related to that intent, consider removing the intent from your config.","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:03.3688717+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway Connected","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:03.9425506+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway Ready","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}} +{"Timestamp":"2022-09-13T02:59:10.5882097+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/index.html","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/index.html - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000001","RequestPath":"/index.html","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/index.html - -"}]}} +{"Timestamp":"2022-09-13T02:59:11.5711332+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":996.3451,"StatusCode":200,"ContentType":"text/html;charset=utf-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/index.html","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/index.html - - - 200 - text/html;charset=utf-8 996.3451ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000001","RequestPath":"/index.html","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/index.html - - - 200 - text/html;charset=utf-8 996.3451ms"}]}} +{"Timestamp":"2022-09-13T02:59:11.6123850+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_framework/aspnetcore-browser-refresh.js","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000003","RequestPath":"/_framework/aspnetcore-browser-refresh.js","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - -"}]}} +{"Timestamp":"2022-09-13T02:59:11.6281716+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":15.5581,"StatusCode":200,"ContentType":"application/javascript; charset=utf-8","ContentLength":11994,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_framework/aspnetcore-browser-refresh.js","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - - - 200 11994 application/javascript;+charset=utf-8 15.5581ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000003","RequestPath":"/_framework/aspnetcore-browser-refresh.js","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - - - 200 11994 application/javascript;+charset=utf-8 15.5581ms"}]}} +{"Timestamp":"2022-09-13T02:59:11.7015500+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_vs/browserLink","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/_vs/browserLink - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000005","RequestPath":"/_vs/browserLink","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/_vs/browserLink - -"}]}} +{"Timestamp":"2022-09-13T02:59:11.8214511+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":119.7799,"StatusCode":200,"ContentType":"text/javascript; charset=UTF-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_vs/browserLink","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/_vs/browserLink - - - 200 - text/javascript;+charset=UTF-8 119.7799ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000005","RequestPath":"/_vs/browserLink","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/_vs/browserLink - - - 200 - text/javascript;+charset=UTF-8 119.7799ms"}]}} +{"Timestamp":"2022-09-13T02:59:11.9652804+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/swagger/v1/swagger.json","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000007","RequestPath":"/swagger/v1/swagger.json","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - -"}]}} +{"Timestamp":"2022-09-13T02:59:12.3915820+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":426.2987,"StatusCode":200,"ContentType":"application/json;charset=utf-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/swagger/v1/swagger.json","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - - - 200 - application/json;charset=utf-8 426.2987ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000007","RequestPath":"/swagger/v1/swagger.json","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - - - 200 - application/json;charset=utf-8 426.2987ms"}]}} diff --git a/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj b/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj new file mode 100644 index 0000000..405284d --- /dev/null +++ b/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs new file mode 100644 index 0000000..7fa7423 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs @@ -0,0 +1,12 @@ +using Managing.Domain.Accounts; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IAccountRepository +{ + Task GetAccountByNameAsync(string name); + Task GetAccountByKeyAsync(string key); + Task InsertAccountAsync(Account account); + void DeleteAccountByName(string name); + IEnumerable GetAccounts(); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs new file mode 100644 index 0000000..314218c --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IBacktestRepository.cs @@ -0,0 +1,12 @@ + +using Managing.Domain.Backtests; + +namespace Managing.Application.Abstractions; + +public interface IBacktestRepository +{ + void InsertBacktest(Backtest result); + IEnumerable GetBacktests(); + void DeleteBacktestById(string id); + void DeleteAllBacktests(); +} diff --git a/src/Managing.Application.Abstractions/Repositories/ICandleRepository.cs b/src/Managing.Application.Abstractions/Repositories/ICandleRepository.cs new file mode 100644 index 0000000..5aa4c93 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/ICandleRepository.cs @@ -0,0 +1,18 @@ +using Managing.Common; +using Managing.Domain.Candles; + +namespace Managing.Application.Abstractions.Repositories; + +public interface ICandleRepository +{ + Task> GetCandles( + Enums.TradingExchanges exchange, + Enums.Ticker ticker, + Enums.Timeframe timeframe, + DateTime start); + Task> GetTickersAsync( + Enums.TradingExchanges exchange, + Enums.Timeframe timeframe, + DateTime start); + void InsertCandle(Candle candle); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs b/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs new file mode 100644 index 0000000..fb50b97 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs @@ -0,0 +1,36 @@ +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Evm; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IEvmManager +{ + (string Key, string Secret) GenerateAddress(); + string GetAddressFromMnemo(string mnemo); + Task GetAddressBalance(string address); + Task GetBlockDate(int blockNumber); + Task> GetContractHolders(string contractAddress, DateTime since); + string SignMessage(string message, string privateKey); + string VerifySignature(string signature, string message); + Task> GetBalances(Chain chain, int page, int pageSize, string publicAddress); + Task> GetAllBalancesOnAllChain(string publicAddress); + Task> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe interval); + decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker); + Task> GetAvailableTicker(); + Task GetCandle(SubgraphProvider subgraphProvider, Ticker ticker); + Task InitAddress(string chainName, string publicAddress, string privateKey); + Task Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, string receiverAddress); + Task GetTokenBalance(string chainName, Ticker ticker, string publicAddress); + Task CancelOrders(Account account, Ticker ticker); + Task IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = 1); + Task GetTrade(Account account, string chainName, Ticker ticker); + Task DecreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage); + Task QuantityInPosition(string chainName, string publicAddress, Ticker ticker); + Task DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage); + Task GetFee(string chainName); + Task> GetOrders(Account account, Ticker ticker); + Task GetTrade(string reference, string arbitrum, Ticker ticker); +} diff --git a/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs b/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs new file mode 100644 index 0000000..6aae113 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/ISettingsRepository.cs @@ -0,0 +1,13 @@ +using Managing.Domain.MoneyManagements; + +namespace Managing.Application.Abstractions.Repositories; + +public interface ISettingsRepository +{ + Task GetMoneyManagement(string name); + Task InsertMoneyManagement(MoneyManagement request); + void UpdateMoneyManagement(MoneyManagement moneyManagement); + IEnumerable GetMoneyManagements(); + void DeleteMoneyManagement(string name); + void DeleteMoneyManagements(); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs b/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs new file mode 100644 index 0000000..3b583fb --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IStatisticRepository.cs @@ -0,0 +1,20 @@ +using Managing.Domain.Statistics; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IStatisticRepository +{ + Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker); + IList GetTopVolumeTickers(DateTime date); + Task SaveSpotligthtOverview(SpotlightOverview overview); + IList GetSpotlightOverviews(DateTime date); + void UpdateSpotlightOverview(SpotlightOverview overview); + List GetBestTraders(); + void UpdateBestTrader(Trader trader); + Task InsertBestTrader(Trader trader); + Task RemoveBestTrader(Trader trader); + List GetBadTraders(); + void UpdateBadTrader(Trader trader); + Task InsertBadTrader(Trader trader); + Task RemoveBadTrader(Trader trader); +} diff --git a/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs new file mode 100644 index 0000000..5f6b0e1 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs @@ -0,0 +1,29 @@ +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions.Repositories; + +public interface ITradingRepository +{ + Scenario GetScenarioByName(string scenario); + void InsertSignal(Signal signal); + void InsertPosition(Position position); + void UpdatePosition(Position position); + Strategy GetStrategyByName(string strategy); + void InsertScenario(Scenario scenario); + void InsertStrategy(Strategy strategy); + IEnumerable GetScenarios(); + IEnumerable GetStrategies(); + void DeleteScenario(string name); + void DeleteStrategy(string name); + void DeleteScenarios(); + void DeleteStrategies(); + Position GetPositionByIdentifier(string identifier); + IEnumerable GetPositions(PositionInitiator positionInitiator); + IEnumerable GetPositionsByStatus(PositionStatus positionStatus); + Fee GetFee(TradingExchanges exchange); + void InsertFee(Fee fee); + void UpdateFee(Fee fee); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs new file mode 100644 index 0000000..da98f3c --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs @@ -0,0 +1,9 @@ +using Managing.Domain.Users; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IUserRepository +{ + Task GetUserByNameAsync(string name); + Task InsertUserAsync(User user); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs b/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs new file mode 100644 index 0000000..6f11a7b --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IWorkerRepository.cs @@ -0,0 +1,14 @@ +using Managing.Common; +using Managing.Domain.Workers; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IWorkerRepository +{ + Task DisableWorker(Enums.WorkerType workerType); + Task EnableWorker(Enums.WorkerType workerType); + Task GetWorkerAsync(Enums.WorkerType workerType); + IEnumerable GetWorkers(); + Task InsertWorker(Worker worker); + Task UpdateWorker(Enums.WorkerType workerType, int executionCount); +} diff --git a/src/Managing.Application.Abstractions/Repositories/IWorkflowRepository.cs b/src/Managing.Application.Abstractions/Repositories/IWorkflowRepository.cs new file mode 100644 index 0000000..85e0ba4 --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IWorkflowRepository.cs @@ -0,0 +1,12 @@ +using Managing.Domain.Workflows.Synthetics; + +namespace Managing.Application.Abstractions.Repositories; + +public interface IWorkflowRepository +{ + bool DeleteWorkflow(string name); + Task GetWorkflow(string name); + IEnumerable GetWorkflows(); + Task InsertWorkflow(SyntheticWorkflow workflow); + Task UpdateWorkflow(SyntheticWorkflow workflow); +} diff --git a/src/Managing.Application.Abstractions/Services/IAccountService.cs b/src/Managing.Application.Abstractions/Services/IAccountService.cs new file mode 100644 index 0000000..5c96632 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IAccountService.cs @@ -0,0 +1,16 @@ +using Managing.Domain.Accounts; +using Managing.Domain.Users; + +namespace Managing.Application.Abstractions.Services; + +public interface IAccountService +{ + Task CreateAccount(User user, Account account); + bool DeleteAccount(User user, string name); + IEnumerable GetAccountsByUser(User user, bool hideSecrets = true); + IEnumerable GetAccounts(bool hideSecrets, bool getBalance); + Task GetAccount(string name, bool hideSecrets, bool getBalance); + Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance); + Task GetAccountByKey(string key, bool hideSecrets, bool getBalance); + IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets = true); +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs new file mode 100644 index 0000000..799ac4e --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs @@ -0,0 +1,20 @@ +using Managing.Domain.Accounts; +using Managing.Domain.Backtests; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions +{ + 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); + Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false, bool save = false); + IEnumerable GetBacktests(); + bool DeleteBacktest(string id); + bool DeleteBacktests(); + Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List candles, decimal balance); + Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List candles, decimal balance); + } +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IDiscordService.cs b/src/Managing.Application.Abstractions/Services/IDiscordService.cs new file mode 100644 index 0000000..e5d1a3a --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IDiscordService.cs @@ -0,0 +1,19 @@ +using Managing.Domain.Statistics; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions.Services; + +public interface IDiscordService +{ + Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe); + Task SendPosition(Position position); + Task SendClosingPosition(Position position); + Task SendMessage(string v); + Task SendTradeMessage(string message, bool isBadBehavior = false); + Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null); + Task SendClosedPosition(string address, Trade oldTrade); + Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount); + Task SendBestTraders(List traders); + Task SendBadTraders(List traders); +} diff --git a/src/Managing.Application.Abstractions/Services/IExchangeService.cs b/src/Managing.Application.Abstractions/Services/IExchangeService.cs new file mode 100644 index 0000000..9759a06 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IExchangeService.cs @@ -0,0 +1,45 @@ +using Managing.Domain.Trades; +using Managing.Domain.Candles; +using static Managing.Common.Enums; +using Managing.Domain.Accounts; + +namespace Managing.Application.Abstractions.Services; + +public interface IExchangeService +{ + Task 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); + Task GetBalance(Account account, bool isForPaperTrading = false); + Task> GetBalances(Account account, bool isForPaperTrading = false); + decimal GetPrice(Account account, Ticker ticker, DateTime date); + Task GetTrade(Account account, string order, Ticker ticker); + Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); + Task OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice, + decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null); + Task> GetTickers(Account account, Timeframe timeframe); + Task OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice, + decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null); + Task ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false); + decimal GetVolume(Account account, Ticker ticker); + Task> GetTrades(Account account, Ticker ticker); + Task CancelOrder(Account account, Ticker ticker); + decimal GetFee(Account account, bool isForPaperTrading = false); + Candle GetCandle(Account account, Ticker ticker, DateTime date); + Task GetQuantityInPosition(Account account, Ticker ticker); + Task> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe); + decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction); + Orderbook GetOrderbook(Account account, Ticker ticker); + Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, TradeType tradeType, DateTime dateTime, TradeStatus tradeStatus = TradeStatus.PendingOpen); + Task> GetOpenOrders(Account account, Ticker ticker); + Task GetTrade(string reference, string orderId, Ticker ticker); +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IExchangeStream.cs b/src/Managing.Application.Abstractions/Services/IExchangeStream.cs new file mode 100644 index 0000000..0c19dd6 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IExchangeStream.cs @@ -0,0 +1,10 @@ +using Managing.Common; +using Managing.Domain.Candles; + +namespace Managing.Application.Abstractions.Services; + +public interface IExchangeStream +{ + Task StartBinanceWorker(Enums.Ticker ticker, Func action); + Task StopBinanceWorker(); +} diff --git a/src/Managing.Application.Abstractions/Services/IMessengerService.cs b/src/Managing.Application.Abstractions/Services/IMessengerService.cs new file mode 100644 index 0000000..0d7817b --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IMessengerService.cs @@ -0,0 +1,19 @@ +using Managing.Domain.Statistics; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions.Services; + +public interface IMessengerService +{ + Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe); + Task SendPosition(Position position); + Task SendClosingPosition(Position position); + Task SendMessage(string v); + Task SendTradeMessage(string message, bool isBadBehavior = false); + Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null); + Task SendClosedPosition(string address, Trade oldTrade); + Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount); + Task SendBestTraders(List traders); + Task SendBadTraders(List filteredTrader); +} diff --git a/src/Managing.Application.Abstractions/Services/IStreamService.cs b/src/Managing.Application.Abstractions/Services/IStreamService.cs new file mode 100644 index 0000000..8ef92c7 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IStreamService.cs @@ -0,0 +1,7 @@ +namespace Managing.Application.Abstractions.Services; + +public interface IStreamService +{ + Task SubscribeCandle(); + Task UnSubscribeCandle(); +} diff --git a/src/Managing.Application.Abstractions/Services/ITickerService.cs b/src/Managing.Application.Abstractions/Services/ITickerService.cs new file mode 100644 index 0000000..c1cb4ee --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/ITickerService.cs @@ -0,0 +1,8 @@ +using Managing.Common; + +namespace Managing.Application.Abstractions.Services; + +public interface ITickerService +{ + Task> GetAvailableTicker(); +} diff --git a/src/Managing.Application.Abstractions/Services/ITradaoService.cs b/src/Managing.Application.Abstractions/Services/ITradaoService.cs new file mode 100644 index 0000000..af6b2ef --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/ITradaoService.cs @@ -0,0 +1,11 @@ +using Managing.Domain.Statistics; +using Managing.Domain.Trades; + +namespace Managing.Application.Abstractions.Services; + +public interface ITradaoService +{ + Task> GetBadTrader(); + Task> GetBestTrader(); + Task> GetTrades(string address); +} diff --git a/src/Managing.Application.Abstractions/Services/ITradingService.cs b/src/Managing.Application.Abstractions/Services/ITradingService.cs new file mode 100644 index 0000000..df7cc03 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/ITradingService.cs @@ -0,0 +1,35 @@ +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Scenarios; +using Managing.Domain.Statistics; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions.Services; + +public interface ITradingService +{ + Scenario GetScenarioByName(string scenario); + void InsertSignal(Signal signal); + void InsertPosition(Position position); + void UpdatePosition(Position position); + Strategy GetStrategyByName(string strategy); + void InsertScenario(Scenario scenario); + void InsertStrategy(Strategy strategy); + IEnumerable GetScenarios(); + IEnumerable GetStrategies(); + void DeleteScenario(string name); + void DeleteStrategy(string name); + void DeleteScenarios(); + void DeleteStrategies(); + Position GetPositionByIdentifier(string identifier); + IEnumerable GetPositions(PositionInitiator positionInitiator); + IEnumerable GetPositions(); + IEnumerable GetPositionsByStatus(Enums.PositionStatus positionStatus); + Task ManagePosition(Account account, Position position); + void UpdateFee(TradingExchanges evm); + decimal GetFee(Account account, bool isForPaperTrading = false); + Task WatchTrader(); + IEnumerable GetTradersWatch(); +} \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IUserService.cs b/src/Managing.Application.Abstractions/Services/IUserService.cs new file mode 100644 index 0000000..36349a5 --- /dev/null +++ b/src/Managing.Application.Abstractions/Services/IUserService.cs @@ -0,0 +1,9 @@ +using Managing.Domain.Users; + +namespace Managing.Application.Abstractions.Services; + +public interface IUserService +{ + Task Authenticate(string name, string address, string message, string signature); + Task GetUserByAddressAsync(string address); +} diff --git a/src/Managing.Application.Tests/BaseTests.cs b/src/Managing.Application.Tests/BaseTests.cs new file mode 100644 index 0000000..17a344d --- /dev/null +++ b/src/Managing.Application.Tests/BaseTests.cs @@ -0,0 +1,51 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Abstractions; +using Moq; +using Managing.Domain.MoneyManagements; +using static Managing.Common.Enums; +using Managing.Domain.Accounts; + +namespace Managing.Application.Tests; + +public class BaseTests +{ + public readonly Mock _moneyManagementService; + public readonly Mock _accountService; + public readonly IExchangeService _exchangeService; + public readonly Mock _tradingService; + public readonly Account Account; + public readonly MoneyManagement MoneyManagement; + + public const string PublicAddress = "0x0425dEAb364E9121F7CA284129dA854FD5cF22eD"; + public const string PrivateKey = ""; + + public BaseTests() + { + _accountService = new Mock(); + _moneyManagementService = new Mock(); + MoneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.30m, + Leverage = 2, + Timeframe = Timeframe.FifteenMinutes, + StopLoss = 0.008m, + TakeProfit = 0.02m, + Name = "Default MM" + }; + + _ =_moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny())).Returns(Task.FromResult(MoneyManagement)); + + Account = new Account() + { + Exchange = TradingExchanges.Evm, + Key = PublicAddress, + Secret = PrivateKey, + }; + + _accountService.Setup(a => a.GetAccount(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(Account)); + + _tradingService = new Mock(); + _exchangeService = TradingBaseTests.GetExchangeService(); + } +} diff --git a/src/Managing.Application.Tests/BotsTests.cs b/src/Managing.Application.Tests/BotsTests.cs new file mode 100644 index 0000000..3f7400d --- /dev/null +++ b/src/Managing.Application.Tests/BotsTests.cs @@ -0,0 +1,408 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Backtesting; +using Managing.Application.Bots.Base; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Moq; +using System.Collections; +using System.Diagnostics; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests +{ + public class BotsTests : BaseTests + { + private readonly IBotFactory _botFactory; + private readonly IBacktester _backtester; + private readonly string _reportPath = "D:\\BacktestingReports\\backtesting.csv"; + private string _analysePath = "D:\\BacktestingReports\\analyse"; + private readonly string _errorsPath = "D:\\BacktestingReports\\errorsAnalyse.csv"; + private readonly string _s = "|"; + private List _elapsedTimes { get; set; } + + public BotsTests() : base () + { + var backtestRepository = new Mock().Object; + var discordService = new Mock().Object; + var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); + var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); + _botFactory = new BotFactory( + _exchangeService, + tradingBotLogger, + _moneyManagementService.Object, + discordService, + _accountService.Object, + _tradingService.Object); + _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger); + _elapsedTimes = new List(); + } + + [Theory] + [InlineData(Ticker.BTC, Timeframe.OneDay, -100)] + public void SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var scenario = new Scenario("ScalpingScenario"); + var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 14); + scenario.AddStrategy(strategy); + + // Act + var backtestResult = _backtester.RunFlippingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days*2), 1000); + WriteCsvReport(backtestResult.GetStringReport()); + + // Assert + Assert.True(backtestResult.FinalPnl > 0); + Assert.True(backtestResult.WinRate >= 30); + Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); + } + + [Theory] + //[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.ThirtyMinutes, -5)] + //[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.FifteenMinutes, -5)] + //[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.ThirtyMinutes, -4)] + //[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.FifteenMinutes, -4)] + //[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, int days) + { + // Arrange + var scenario = new Scenario("ScalpingScenario"); + var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 5); + scenario.AddStrategy(strategy); + + // Act + var backtestResult = _backtester.RunScalpingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000); + //WriteCsvReport(backtestResult.GetStringReport()); + + // Assert + Assert.True(backtestResult.FinalPnl > 0); + Assert.True(backtestResult.WinRate >= 30); + Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); + } + + [Theory] + [InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -8)] + public void MacdCross_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var scenario = new Scenario("ScalpingScenario"); + var strategy = ScenarioHelpers.BuildStrategy(StrategyType.MacdCross, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9); + scenario.AddStrategy(strategy); + + var moneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.05m, + Leverage = 1, + Timeframe = timeframe, + StopLoss = 0.01m, + TakeProfit = 0.02m + }; + + // Act + var backtestResult = _backtester.RunScalpingBotBacktest(Account, moneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000); + WriteCsvReport(backtestResult.GetStringReport()); + + // Assert + Assert.True(backtestResult.FinalPnl > 0); + Assert.True(backtestResult.WinRate >= 30); + Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage); + } + + [Theory] + [InlineData(Timeframe.FifteenMinutes, -6, StrategyType.RsiDivergenceConfirm, BotType.ScalpingBot)] + //[InlineData(Timeframe.FifteenMinutes, -6, Enums.StrategyType.RsiDivergenceConfirm, Enums.BotType.FlippingBot)] + public void GetBestPeriodRsiForDivergenceFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType) + { + var result = new List>(); + var errors = new List(); + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = 4 + }; + + var periodRange = new List() { 2, 7}; + var stopLossRange = new List() { 0.005m, 0.05m, 0.005m }; + var takeProfitRange = new List() { 0.01m, 0.1m, 0.02m }; + var fileIdentifier = $"{strategyType}-{timeframe}"; + var completedTest = 0; + var totalTests = GetTotalTrades(periodRange, stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length; + + CleanAnalyseFile(fileIdentifier); + UpdateProgression(totalTests, completedTest); + + Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => { + + var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result; + + if (candles == null || candles.Count == 0) + return; + + Parallel.For(periodRange[0], periodRange[1], options, i => { + var scenario = new Scenario("ScalpingScenario"); + var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", period: i); + scenario.AddStrategy(strategy); + + // -0.5 to -5 + for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2]) + { + // +1% to +10% in 1% + for(decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2]) + { + var moneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.05m, + Leverage = 1, + Timeframe = timeframe, + StopLoss = s, + TakeProfit = t + }; + + try + { + var timer = new Stopwatch(); + timer.Start(); + + var backtestResult = botType switch + { + BotType.SimpleBot => throw new NotImplementedException(), + BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), + BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), + _ => throw new NotImplementedException(), + }; + timer.Stop(); + + if (backtestResult.FinalPnl > 0 + && (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30 + && backtestResult.Statistics.MaxDrawdown < 3) + { + var currentResult = new Tuple(ticker.ToString(), i, + backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage); + result.Add(currentResult); + } + + completedTest++; + UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds); + } + catch (Exception ex) + { + completedTest++; + errors.Add($"{ticker}{_s}{i}{_s}{s}{_s}{t}{_s}{ex.Message}"); + } + } + } + }); + }); + + foreach (var r in result) + { + WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2}{_s}{r.Item3.ToString("0.000")}{_s}{r.Item4 * 100}{_s}{r.Item5 * 100}{_s}{r.Item6}"); + } + + foreach (var e in errors) + { + WriteCsvErrors(e); + } + + var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault(); + WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%"); + + Assert.True(result.Any()); + } + + + [Theory] + [InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.FlippingBot)] + [InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.ScalpingBot)] + public void GetBestMMForMacdFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType) + { + var result = new List>(); + var errors = new List(); + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = 4 + }; + + var stopLossRange = new List() { 0.005m, 0.05m, 0.005m }; + var takeProfitRange = new List() { 0.01m, 0.1m, 0.02m }; + var fileIdentifier = $"{strategyType}-{timeframe}-{botType}"; + var completedTest = 0; + var totalTests = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length; + + CleanAnalyseFile(fileIdentifier); + UpdateProgression(totalTests, completedTest); + + Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => { + + var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result; + + if (candles == null || candles.Count == 0) + return; + + var scenario = new Scenario("ScalpingScenario"); + var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9); + scenario.AddStrategy(strategy); + + // -0.5 to -5 + for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2]) + { + // +1% to +10% in 1% + for (decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2]) + { + var timer = new Stopwatch(); + timer.Start(); + try + { + var moneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.05m, + Leverage = 1, + Timeframe = timeframe, + StopLoss = s, + TakeProfit = t + }; + + var backtestResult = botType switch + { + BotType.SimpleBot => throw new NotImplementedException(), + BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), + BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000), + _ => throw new NotImplementedException(), + }; + + if (backtestResult.FinalPnl > 0 + && (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30 + && backtestResult.Statistics.MaxDrawdown < 3) + { + var currentResult = new Tuple(ticker.ToString(), + backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage); + result.Add(currentResult); + } + + completedTest++; + UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds); + } + catch (Exception ex) + { + completedTest++; + errors.Add($"{ticker}{_s}{s}{_s}{t}{_s}{ex.Message}"); + } + timer.Stop(); + } + } + }); + + foreach (var r in result) + { + WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2.ToString("0.000")}{_s}{r.Item3 * 100}{_s}{r.Item4 * 100}{_s}{r.Item5}"); + } + + foreach (var e in errors) + { + WriteCsvErrors(e); + } + + var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault(); + WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%"); + + Assert.True(result.Any()); + } + + + private void WriteCsvReport(string line) + { + File.AppendAllLines(_reportPath, new[] { line }); + } + + private void WriteCsvAnalyse(string line, string fileIdentifier = null) + { + if (!string.IsNullOrEmpty(fileIdentifier)) + _analysePath += $"-{fileIdentifier}-{DateTime.Now.ToString("dd-MM-HH-mm")}.csv"; + + File.AppendAllLines(_analysePath , new[] { line }); + } + + private void WriteCsvErrors(string line) + { + File.AppendAllLines(_errorsPath, new[] { line }); + } + + private void CleanAnalyseFile(string fileIdentifier) + { + WriteCsvAnalyse("", fileIdentifier); + } + + private decimal GetTotalTrades(List periodRange, List stopLossRange, List takeProfitRange) + { + var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2]; + var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2]; + + var totalTrades = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * stopLossRangeTotalTest * takeProfitRangeTotalTest; + return totalTrades; + } + + private decimal GetTotalTradeForStopLossTakeProfit(List stopLossRange, List takeProfitRange) + { + var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2]; + var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2]; + + var totalTrades = stopLossRangeTotalTest * takeProfitRangeTotalTest; + return totalTrades; + } + + + private void UpdateProgression(decimal totalTest, int completedTest, double? elapsed = null) + { + var timeRemaining = ""; + if (elapsed.HasValue && completedTest > 0) + { + //_elapsedTimes.Add((elapsed.Value / completedTest) * ((double)totalTest - completedTest)); + _elapsedTimes.Add(elapsed.Value); + //var t = (_elapsedTimes.Average() / completedTest) * ((double)totalTest - completedTest); + var t = (_elapsedTimes.Average() * (double)(totalTest - completedTest)); + timeRemaining = $" Remaining time: {t} seconds - Estimated end date: {DateTime.Now.AddSeconds(t)}"; + } + + ModifyFirstRow(_analysePath, $"{completedTest}/{totalTest}{timeRemaining}"); + } + + private void ModifyFirstRow(string filepath, string newValue) + { + ArrayList rows = new ArrayList(); + + using (StreamReader reader = new StreamReader(filepath)) + { + string row = null; + + while ((row = reader.ReadLine()) != null) + { + rows.Add(row); + } + } + + // Determ if the file even contains rows. + if (rows.Count > 0) + { + // Replace the first row. + rows[0] = newValue; + } + else + { + // Add the new value because there + // where no rows found in the file. + rows.Add(newValue); + } + + // Write the modified content to the file. + using (StreamWriter writer = new StreamWriter(filepath, false)) + { + foreach (String row in rows) + { + writer.WriteLine(row); + } + } + } + } +} diff --git a/src/Managing.Application.Tests/CandleHelpersTests.cs b/src/Managing.Application.Tests/CandleHelpersTests.cs new file mode 100644 index 0000000..4151045 --- /dev/null +++ b/src/Managing.Application.Tests/CandleHelpersTests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Managing.Application.Tests +{ + public class CandleHelpersTests + { + [Fact] + public void Shoud_Result_Correct_Range_Date() + { + // Arrange + var expectedDate = DateTime.Now.AddMinutes(-15*5); + var currentCandleDate = DateTime.Now; + var previousCandleDate = DateTime.Now.AddMinutes(-15); + + // Act + //var result = CandleHelpers.GetRangeDateFromTimeframe + // Assert + } + } +} diff --git a/src/Managing.Application.Tests/Managing.Application.Tests.csproj b/src/Managing.Application.Tests/Managing.Application.Tests.csproj new file mode 100644 index 0000000..d91fda2 --- /dev/null +++ b/src/Managing.Application.Tests/Managing.Application.Tests.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + enable + AnyCPU + AnyCPU;x64 + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/Managing.Application.Tests/MathHelpersTests.cs b/src/Managing.Application.Tests/MathHelpersTests.cs new file mode 100644 index 0000000..c333754 --- /dev/null +++ b/src/Managing.Application.Tests/MathHelpersTests.cs @@ -0,0 +1,24 @@ +using Managing.Core; +using Xunit; + +namespace Managing.Application.Tests +{ + public class MathHelpersTests + { + [Theory] + [InlineData(0.00010, 4)] + public void Should_Return_Correct_Precision(decimal n, int expectedValue) + { + var precision = MathHelpers.GetDecimalPlaces(n); + Assert.Equal(expectedValue, precision); + } + + [Theory] + [InlineData("0.00010", 4)] + public void Should_Return_Correct_PrecisionTest(string s, int expectedValue) + { + var precision = MathHelpers.GetDecimalPlaces(s); + Assert.Equal(expectedValue, precision); + } + } +} diff --git a/src/Managing.Application.Tests/PositionTests.cs b/src/Managing.Application.Tests/PositionTests.cs new file mode 100644 index 0000000..9c613c8 --- /dev/null +++ b/src/Managing.Application.Tests/PositionTests.cs @@ -0,0 +1,56 @@ +using Managing.Application.Trading; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using Moq; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests; + +public class PositionTests : BaseTests +{ + public PositionTests() : base() + { + } + + [Fact] + public async void Should_Open_Position() + { + var command = new OpenPositionRequest( + "test", + MoneyManagement, + TradeDirection.Short, + Ticker.BTC, + PositionInitiator.User, + DateTime.UtcNow, + isForPaperTrading: false); + var handler = new OpenPositionCommandHandler( + _exchangeService, + _accountService.Object, + _tradingService.Object); + + var position = await handler.Handle(command); + + Assert.NotNull(position); + } + + [Fact] + public async void Shoud_Close_Position() + { + var openTrade = await _exchangeService.GetTrade(Account, "", Ticker.BTC); + var position = new Position("", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User, DateTime.UtcNow) + { + Open = openTrade + }; + var command = new ClosePositionCommand(position); + _ = _tradingService.Setup(m => m.GetPositionByIdentifier(It.IsAny())).Returns(position); + + var handler = new ClosePositionCommandHandler( + _exchangeService, + _accountService.Object, + _tradingService.Object); + + var closedPosition = await handler.Handle(command); + Assert.NotNull(closedPosition); + } +} diff --git a/src/Managing.Application.Tests/ProfitAndLossTests.cs b/src/Managing.Application.Tests/ProfitAndLossTests.cs new file mode 100644 index 0000000..11752d2 --- /dev/null +++ b/src/Managing.Application.Tests/ProfitAndLossTests.cs @@ -0,0 +1,251 @@ +using Managing.Domain.Trades; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests +{ + public class ProfitAndLossTests + { + [Theory] + [InlineData(1, 100, 110, 10)] + [InlineData(2, 100, 110, 20)] + public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice, decimal expectedResult) + { + // Arrange + var init = new List>(); + init.Add(new Tuple(quantity, price)); + + var pnl = new ProfitAndLoss(init, TradeDirection.Long); + + // Act + var result = pnl.FloatingForTheoriticalExit(exitPrice); + + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit() + { + // Arrange + var position = GetFakeShortPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Trigger Stop Loss + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection); + + // Assert + Assert.Equal(20, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit() + { + // Arrange + var position = GetSolanaLongPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Trigger Stop Loss + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection); + + // Assert + Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit2() + { + // Arrange + var position = GetSolanaLongPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price), + new Tuple(-position.TakeProfit2.Quantity, position.StopLoss.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Assert + Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit2() + { + // Arrange + var position = GetFakeShortPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price), + new Tuple(-position.TakeProfit2.Quantity, position.StopLoss.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Assert + Assert.Equal(20, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit3() + { + // Arrange + var position = GetFakeShortPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price, TradeDirection.Short); + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Short); + + // Assert + Assert.Equal(20, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Short_Succeeded_Position_After_Two_Take_Profit() + { + // Arrange + var position = GetFakeShortPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Trigger Stop Loss + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Short); + + // Assert + Assert.Equal(120, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Long_failed_Position_After_Took_One_Profit() + { + // Arrange + var position = GetFakeLongPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Trigger Stop Loss + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Long); + + // Assert + Assert.Equal(20, position.ProfitAndLoss.Realized); + } + + [Fact] + public void Should_Return_Correct_Pnl_For_Long_Succeeded_Position_After_Two_Take_Profit() + { + // Arrange + var position = GetFakeLongPosition(); + + // Setup Open and first take profit + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-position.TakeProfit1.Quantity, position.TakeProfit1.Price) + }; + + // Act + position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection); + + // Trigger Stop Loss + position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Long); + + // Assert + Assert.Equal(120, position.ProfitAndLoss.Realized); + } + + private static Position GetFakeShortPosition() + { + return new Position("FakeAccount", TradeDirection.Short, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow) + { + Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled, + TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""), + StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.StopMarket, Ticker.ADA, 10, 110, 1, "StopLossOrderId", ""), + TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, 6, 90, 1, "TakeProfit1OrderId", ""), + TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, 4, 85, 1, "TakeProfit1OrderId", "") + }; + } + + private static Position GetSolanaLongPosition() + { + return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow) + { + Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled, + TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1, "OpenOrderId", ""), + StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen, + TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1, "StopLossOrderId", ""), + TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000, 1, "TakeProfit1OrderId", ""), + TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000, 1, "TakeProfit1OrderId", "") + }; + } + + private static Position GetFakeLongPosition() + { + return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow) + { + Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled, + TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""), + StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.StopMarket, Ticker.ADA, 10, 90, 1, "StopLossOrderId", ""), + TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, 6, 110, 1, "TakeProfit1OrderId", ""), + TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen, + TradeType.TakeProfitLimit, Ticker.ADA, 4, 115, 1, "TakeProfit1OrderId", "") + }; + } + } +} diff --git a/src/Managing.Application.Tests/RiskHelpersTests.cs b/src/Managing.Application.Tests/RiskHelpersTests.cs new file mode 100644 index 0000000..6bdf5e0 --- /dev/null +++ b/src/Managing.Application.Tests/RiskHelpersTests.cs @@ -0,0 +1,62 @@ +using Managing.Domain.MoneyManagements; +using Managing.Domain.Shared.Helpers; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests +{ + public class RiskHelpersTests + { + private readonly MoneyManagement _moneyManagement; + public RiskHelpersTests() + { + _moneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.05m, + Leverage = 1, + Timeframe = Timeframe.OneDay, + StopLoss = 0.008m, + TakeProfit = 0.02m + }; + } + + [Theory] + [InlineData(1000, 992)] + public void Should_Return_Correct_Stop_Loss_Price_For_Long(decimal price, decimal expectedResult) + { + var stopLossPrice = RiskHelpers.GetStopLossPrice(TradeDirection.Long, price, _moneyManagement); + Assert.Equal(expectedResult, stopLossPrice); + } + + [Theory] + [InlineData(1000, 1008)] + public void Should_Return_Correct_Stop_Loss_Price_For_Short(decimal price, decimal expectedResult) + { + var stopLossPrice = RiskHelpers.GetStopLossPrice(TradeDirection.Short, price, _moneyManagement); + Assert.Equal(expectedResult, stopLossPrice); + } + + [Theory] + [InlineData(1000, 980)] + public void Should_Return_Correct_Take_Profit_Price_For_Short(decimal price, decimal expectedResult) + { + var stopLossPrice = RiskHelpers.GetTakeProfitPrice(TradeDirection.Short, price, _moneyManagement); + Assert.Equal(expectedResult, stopLossPrice); + } + + [Theory] + [InlineData(1000, 1020)] + public void Should_Return_Correct_Take_Profit_Price_For_Long(decimal price, decimal expectedResult) + { + var stopLossPrice = RiskHelpers.GetTakeProfitPrice(TradeDirection.Long, price, _moneyManagement); + Assert.Equal(expectedResult, stopLossPrice); + } + + [Fact] + public void Test() + { + const decimal test = (decimal)34523.4352; + Assert.Equal((decimal)34523.4, Math.Round(test, 1)); + } + } +} diff --git a/src/Managing.Application.Tests/StrategyTests.cs b/src/Managing.Application.Tests/StrategyTests.cs new file mode 100644 index 0000000..dd074a6 --- /dev/null +++ b/src/Managing.Application.Tests/StrategyTests.cs @@ -0,0 +1,212 @@ +using Managing.Application.Abstractions.Services; +using Managing.Domain.Accounts; +using Managing.Domain.Strategies; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests +{ + public class StrategyTests + { + private readonly IExchangeService _exchangeService; + + public StrategyTests() + { + _exchangeService = TradingBaseTests.GetExchangeService(); + } + + [Theory] + [InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)] + [InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)] + [InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)] + public void Shoud_Return_Signal_On_Rsi_BullishDivergence2(TradingExchanges exchange, Ticker ticker, + Timeframe timeframe) + { + var account = GetAccount(exchange); + // Arrange + var rsiStrategy = new RSIDivergenceStrategy("unittest", timeframe, 5); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + rsiStrategy.Candles.Add(candle); + var signals = rsiStrategy.Run(); + } + + if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0) + resultSignal.AddRange(rsiStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + + private static Account GetAccount(TradingExchanges exchange) + { + return new Account() + { + Exchange = exchange + }; + } + + [Theory] + [InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)] + public void Shoud_Return_Signal_On_Rsi_BearishDivergence(TradingExchanges exchange, Ticker ticker, + Timeframe timeframe) + { + // Arrange + var account = GetAccount(exchange); + var rsiStrategy = new RSIDivergenceStrategy("unittest", timeframe, 5); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + rsiStrategy.Candles.Add(candle); + var signals = rsiStrategy.Run(); + } + + if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0) + resultSignal.AddRange(rsiStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + } + + + [Theory] + [InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)] + public void Shoud_Return_Signal_On_Macd_Cross(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var account = GetAccount(exchange); + var rsiStrategy = new MACDCrossStrategy("unittest", timeframe, 12, 26, 9); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + rsiStrategy.Candles.Add(candle); + var signals = rsiStrategy.Run(); + } + + if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0) + resultSignal.AddRange(rsiStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + + [Theory] + [InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)] + public void Shoud_Return_Signal_On_SuperTrend(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var account = GetAccount(exchange); + var superTrendStrategy = new SuperTrendStrategy("unittest", timeframe, 10, 3); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + superTrendStrategy.Candles.Add(candle); + var signals = superTrendStrategy.Run(); + } + + if (superTrendStrategy.Signals != null && superTrendStrategy.Signals.Count > 0) + resultSignal.AddRange(superTrendStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + + [Theory] + [InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)] + public void Shoud_Return_Signal_On_ChandelierExist(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var account = GetAccount(exchange); + var chandelierExitStrategy = new ChandelierExitStrategy("unittest", timeframe, 22, 3); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + chandelierExitStrategy.Candles.Add(candle); + var signals = chandelierExitStrategy.Run(); + } + + if (chandelierExitStrategy.Signals != null && chandelierExitStrategy.Signals.Count > 0) + resultSignal.AddRange(chandelierExitStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + + [Theory] + [InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)] + public void Shoud_Return_Signal_On_EmaTrend(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var account = GetAccount(exchange); + var emaTrendSrategy = new EmaTrendStrategy("unittest", timeframe, 200); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + emaTrendSrategy.Candles.Add(candle); + var signals = emaTrendSrategy.Run(); + } + + if (emaTrendSrategy.Signals != null && emaTrendSrategy.Signals.Count > 0) + resultSignal.AddRange(emaTrendSrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + + + [Theory] + [InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)] + public void Shoud_Return_Signal_On_StochRsi(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days) + { + // Arrange + var account = GetAccount(exchange); + var stochRsiStrategy = new StochRsiTrendStrategy("unittest", timeframe, 14, 14, 3, 1); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; + var resultSignal = new List(); + + // Act + foreach (var candle in candles) + { + stochRsiStrategy.Candles.Add(candle); + var signals = stochRsiStrategy.Run(); + } + + if (stochRsiStrategy.Signals != null && stochRsiStrategy.Signals.Count > 0) + resultSignal.AddRange(stochRsiStrategy.Signals); + + // Assert + Assert.IsType>(resultSignal); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); + Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); + } + } +} diff --git a/src/Managing.Application.Tests/TradingBaseTests.cs b/src/Managing.Application.Tests/TradingBaseTests.cs new file mode 100644 index 0000000..17a9802 --- /dev/null +++ b/src/Managing.Application.Tests/TradingBaseTests.cs @@ -0,0 +1,85 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Application.Backtesting; +using Managing.Application.Bots; +using Managing.Infrastructure.Databases; +using Managing.Infrastructure.Databases.InfluxDb; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Managing.Infrastructure.Evm; +using Managing.Infrastructure.Evm.Abstractions; +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 Moq; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests +{ + public static class TradingBaseTests + { + public static IExchangeService GetExchangeService() + { + ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory(); + + var ChainlinkGmx = new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx)); + var Chainlink = new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice)); + var GbcFeed = new Gbc(SubgraphService.GetSubgraphClient(SubgraphProvider.Gbc)); + + var Subgraphs = new List + { + ChainlinkGmx, + Chainlink, + GbcFeed + }; + var evmManager = new EvmManager(Subgraphs); + var evmProcessor = new EvmProcessor(new Mock>().Object, evmManager); + + var exchangeProcessors = new List() + { + //new Mock().Object, + //new Mock().Object, + evmProcessor + }; + + return new ExchangeService(loggerFactory.CreateLogger(), GetCandleRepository(), exchangeProcessors); + } + + public static ILogger CreateTradingBotLogger() + { + ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory(); + + return loggerFactory.CreateLogger(); + } + + public static ILogger CreateBacktesterLogger() + { + ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory(); + + return loggerFactory.CreateLogger(); + } + + public static ILogger CreateCandleRepositoryLogger() + { + ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory(); + + return loggerFactory.CreateLogger(); + } + + public static ICandleRepository GetCandleRepository() + { + var settings = new InfluxDbSettings() + { + Url = "http://localhost:8086/", + Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==", + Organization = "managing-org" + }; + var influxdb = new InfluxDbRepository(settings); + var candleRepository = new CandleRepository(influxdb, CreateCandleRepositoryLogger()); + + return candleRepository; + } + } +} diff --git a/src/Managing.Application.Tests/WorkflowTests.cs b/src/Managing.Application.Tests/WorkflowTests.cs new file mode 100644 index 0000000..8961bc2 --- /dev/null +++ b/src/Managing.Application.Tests/WorkflowTests.cs @@ -0,0 +1,54 @@ +using Managing.Application.Workflows.Flows.Feeds; +using Managing.Domain.Workflows; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Application.Tests; + +public class WorkflowTests : BaseTests +{ + [Fact] + public async void Should_Create_Workflow_with_Feed_Ticker_Flow() + { + // Arrange + var workflow = new Workflow + { + Name = "Bot trading", + Usage = WorkflowUsage.Trading, + Description = "Basic trading Workflow", + Flows = new List() + }; + + // var rsiDivFlow = new RsiDiv() + // { + // Parameters = "{\"Period\": 14,\"Timeframe\":1}", + // Children = new List(), + // }; + + // var tickerFlow = new FeedTicker(_exchangeService) + // { + // Parameters = "{\"Exchange\": 3,\"Ticker\":9,\"Timeframe\":1}", + // Children = new List() + // { + // rsiDivFlow + // } + // }; + + // workflow.Flows.Add(tickerFlow); + + // Act + await workflow.Execute(); + + // Assert + foreach (var f in workflow.Flows) + { + Assert.False(string.IsNullOrEmpty(f.Output)); + } + Assert.NotNull(workflow); + Assert.NotNull(workflow.Flows); + Assert.Single(workflow.Flows); + Assert.Equal("Feed Ticker", workflow.Name); + Assert.Equal(WorkflowUsage.Trading, workflow.Usage); + Assert.Equal("Basic trading Workflow", workflow.Description); + } +} diff --git a/src/Managing.Application.Workers/Abstractions/IPricesService.cs b/src/Managing.Application.Workers/Abstractions/IPricesService.cs new file mode 100644 index 0000000..5b6cae9 --- /dev/null +++ b/src/Managing.Application.Workers/Abstractions/IPricesService.cs @@ -0,0 +1,8 @@ +using static Managing.Common.Enums; + +namespace Managing.Application.Workers.Abstractions; + +public interface IPricesService +{ + Task UpdatePrice(TradingExchanges exchange, Ticker ticker, Timeframe timeframe); +} diff --git a/src/Managing.Application.Workers/Abstractions/IStatisticService.cs b/src/Managing.Application.Workers/Abstractions/IStatisticService.cs new file mode 100644 index 0000000..8ab8d08 --- /dev/null +++ b/src/Managing.Application.Workers/Abstractions/IStatisticService.cs @@ -0,0 +1,19 @@ +using Managing.Common; +using Managing.Domain.Statistics; +using Managing.Domain.Trades; + +namespace Managing.Application.Workers.Abstractions; + +public interface IStatisticService +{ + List GetBadTraders(); + List GetBestTraders(); + SpotlightOverview GetLastSpotlight(DateTime dateTime); + IList GetLastTopVolumeTicker(); + Task> GetLeadboardPositons(); + IList GetTickers(); + Task UpdateLeaderboard(); + Task UpdateNoobiesboard(); + Task UpdateSpotlight(); + Task UpdateTopVolumeTicker(Enums.TradingExchanges exchange, int top); +} diff --git a/src/Managing.Application.Workers/Abstractions/IWorkerService.cs b/src/Managing.Application.Workers/Abstractions/IWorkerService.cs new file mode 100644 index 0000000..7063bd3 --- /dev/null +++ b/src/Managing.Application.Workers/Abstractions/IWorkerService.cs @@ -0,0 +1,15 @@ +using Managing.Domain.Workers; +using static Managing.Common.Enums; + +namespace Managing.Application.Workers.Abstractions; + +public interface IWorkerService +{ + Task DisableWorker(WorkerType workerType); + Task EnableWorker(WorkerType workerType); + Task GetWorker(WorkerType workerType); + IEnumerable GetWorkers(); + Task InsertWorker(WorkerType workerType, TimeSpan delay); + Task ToggleWorker(WorkerType workerType); + Task UpdateWorker(WorkerType workerType, int executionCount); +} diff --git a/src/Managing.Application.Workers/Managing.Application.Workers.csproj b/src/Managing.Application.Workers/Managing.Application.Workers.csproj new file mode 100644 index 0000000..75eff83 --- /dev/null +++ b/src/Managing.Application.Workers/Managing.Application.Workers.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Managing.Application.Workers/PricesService.cs b/src/Managing.Application.Workers/PricesService.cs new file mode 100644 index 0000000..77a1b7d --- /dev/null +++ b/src/Managing.Application.Workers/PricesService.cs @@ -0,0 +1,64 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Application.Workers.Abstractions; +using Managing.Domain.Candles; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Application.Workers; + +public class PricesService : IPricesService +{ + private readonly ILogger _logger; + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ICandleRepository _candleRepository; + + public PricesService( + ILogger logger, + IExchangeService exchangeService, + ICandleRepository candleRepository, + IAccountService accountService) + { + _logger = logger; + _exchangeService = exchangeService; + _candleRepository = candleRepository; + _accountService = accountService; + } + + public async Task UpdatePrice(TradingExchanges exchange, Ticker ticker, Timeframe timeframe) + { + try + { + var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); + + if (account == null) + throw new Exception($"Enable to found account for exchange {exchange}"); + + var lastCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe, DateTime.UtcNow.AddDays(-2)); + var lastCandle = lastCandles.LastOrDefault(); + var startDate = lastCandle != null ? lastCandle.Date : CandleExtensions.GetPreloadSinceFromTimeframe(timeframe); + var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe); + + var candles = !lastCandles.Any() ? newCandles : newCandles.Where(c => c.Date > lastCandle?.Date); + var candlesInserted = 0; + + foreach (var newCandle in candles) + { + if (lastCandle == null || newCandle.Date > lastCandle.Date) + { + _candleRepository.InsertCandle(newCandle); + candlesInserted++; + } + } + + if (candlesInserted > 0) + _logger.LogInformation($"[{exchange}][{ticker}][{timeframe}] New candles inserted : {candlesInserted}"); + + } + catch (Exception ex) + { + _logger.LogError($"[{exchange}][{ticker}][{timeframe}] Error : {ex.Message} | {ex.StackTrace}"); + } + } +} \ No newline at end of file diff --git a/src/Managing.Application.Workers/StatisticService.cs b/src/Managing.Application.Workers/StatisticService.cs new file mode 100644 index 0000000..6600beb --- /dev/null +++ b/src/Managing.Application.Workers/StatisticService.cs @@ -0,0 +1,304 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Application.Workers.Abstractions; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Managing.Domain.Shared.Helpers; +using Managing.Domain.Statistics; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Application.Workers; + +public class StatisticService : IStatisticService +{ + private readonly IStatisticRepository _statisticRepository; + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly IEvmManager _evmManager; + private readonly ITradingService _tradingService; + private readonly IBacktester _backtester; + private readonly ITradaoService _tradaoService; + private readonly IMessengerService _messengerService; + private readonly ILogger _logger; + + public StatisticService( + IExchangeService exchangeService, + IAccountService accountService, + ILogger logger, + IStatisticRepository statisticRepository, + IEvmManager evmManager, + ITradingService tradingService, + IBacktester backtester, + ITradaoService tradaoService, + IMessengerService messengerService) + { + _exchangeService = exchangeService; + _accountService = accountService; + _logger = logger; + _statisticRepository = statisticRepository; + _evmManager = evmManager; + _tradingService = tradingService; + _backtester = backtester; + _tradaoService = tradaoService; + _messengerService = messengerService; + } + + public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top) + { + var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); + var date = DateTime.UtcNow; + + if (account == null) + throw new Exception($"Enable to found account for exchange {exchange}"); + + var lastTop = GetLastTopVolumeTicker(); + + if (lastTop != null && lastTop.Count > 0) + { + _logger.LogInformation($"A top of {lastTop.Count} already exist for the current rage"); + return; + } + + var volumeTickers = new Dictionary(); + foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker))) + { + var volume = _exchangeService.GetVolume(account, ticker); + var price = _exchangeService.GetPrice(account, ticker, date); + volumeTickers.Add(ticker, volume * price); + } + + var currentTop = volumeTickers.OrderByDescending(v => v.Value).Take(top).ToList(); + + for (int rank = 0; rank < currentTop.Count; rank++) + { + var dto = new TopVolumeTicker() + { + Date = date, + Rank = rank + 1, + Ticker = currentTop[rank].Key, + Volume = currentTop[rank].Value, + Exchange = exchange + }; + + await _statisticRepository.InsertTopVolumeTicker(dto); + } + } + + public IList GetLastTopVolumeTicker() + { + var from = DateTime.UtcNow.AddDays(-1); + return _statisticRepository.GetTopVolumeTickers(from); + } + + public IList GetTickers() + { + return _evmManager.GetAvailableTicker().Result; + } + + public async Task UpdateSpotlight() + { + var scenarios = _tradingService.GetScenarios(); + var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm); + + if (account == null) + throw new Exception($"Enable to found default account"); + + var overview = GetLastSpotlight(DateTime.Now.AddMinutes(-20)); + + if (overview != null) + { + if(overview.Spotlights.Count < overview.ScenarioCount) + { + _logger.LogInformation($"Spotlights not up to date. {overview.Spotlights.Count}/{overview.ScenarioCount}"); + } + else + { + _logger.LogInformation("No need to update spotlights"); + return; + } + } + else + { + overview = new SpotlightOverview + { + Spotlights = new List(), + DateTime = DateTime.Now, + Identifier = Guid.NewGuid(), + ScenarioCount = scenarios.Count(), + }; + await _statisticRepository.SaveSpotligthtOverview(overview); + } + + var tickers = GetTickers(); + + foreach (var scenario in scenarios) + { + if (overview.Spotlights.Any(s => s.Scenario.Name == scenario.Name)) + continue; + + var spotlight = new Spotlight + { + TickerSignals = new List(), + Scenario = scenario + }; + + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = 2 + }; + + _ = Parallel.ForEach(tickers, options, 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) + }); + }); + + overview.Spotlights.Add(spotlight); + _statisticRepository.UpdateSpotlightOverview(overview); + } + + overview.DateTime = DateTime.Now; + _statisticRepository.UpdateSpotlightOverview(overview); + } + + private List GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe) + { + try + { + var moneyManagement = new MoneyManagement() + { + BalanceAtRisk = 0.05m, + Leverage = 1, + Timeframe = timeframe, + StopLoss = 0.008m, + TakeProfit = 0.02m + }; + + var backtest = _backtester.RunScalpingBotBacktest( + account, + moneyManagement, + ticker, + scenario, + timeframe, + CandleExtensions.GetMinimalDays(timeframe), + 1000, + isForWatchingOnly: true); + + return backtest.Signals; + } + catch (Exception ex) + { + _logger.LogError("Backtest cannot be run", ex); + } + + return null; + } + + public SpotlightOverview GetLastSpotlight(DateTime dateTime) + { + var overviews = _statisticRepository.GetSpotlightOverviews(dateTime); + + if (overviews.Any()) + { + return overviews.OrderBy(o => o.DateTime).Last(); + } + + return null; + } + + public List GetBestTraders() + { + return _statisticRepository.GetBestTraders(); + } + + public List GetBadTraders() + { + return _statisticRepository.GetBadTraders(); + } + + public async Task> GetLeadboardPositons() + { + var customWatchAccount = _tradingService.GetTradersWatch(); + var trades = new List(); + + foreach (var trader in customWatchAccount) + { + trades.AddRange(await _tradaoService.GetTrades(trader.Address)); + } + + return trades; + } + + public async Task UpdateLeaderboard() + { + var previousBestTraders = _statisticRepository.GetBestTraders(); + var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader(); + + // Update / Insert best trader + foreach (var trader in lastBestTrader) + { + if (previousBestTraders.Exists((p) => p.Address == trader.Address)) + { + _statisticRepository.UpdateBestTrader(trader); + } + else + { + await _statisticRepository.InsertBestTrader(trader); + } + } + + // Remove trader that wasnt good enough + foreach (var trader in previousBestTraders) + { + if (!lastBestTrader.Exists((t) => t.Address == trader.Address)) + { + await _statisticRepository.RemoveBestTrader(trader); + } + } + + await _messengerService.SendBestTraders(lastBestTrader); + } + + public async Task UpdateNoobiesboard() + { + var previousBadTraders = _statisticRepository.GetBadTraders(); + var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader(); + + // Update / Insert best trader + foreach (var trader in lastBadTrader) + { + if (previousBadTraders.Exists((p) => p.Address == trader.Address)) + { + _statisticRepository.UpdateBadTrader(trader); + } + else + { + await _statisticRepository.InsertBadTrader(trader); + } + } + + // Remove trader that wasnt good enough + foreach (var trader in previousBadTraders) + { + if (!lastBadTrader.Exists((t) => t.Address == trader.Address)) + { + await _statisticRepository.RemoveBadTrader(trader); + } + } + + await _messengerService.SendBadTraders(lastBadTrader); + } +} diff --git a/src/Managing.Application.Workers/WorkerService.cs b/src/Managing.Application.Workers/WorkerService.cs new file mode 100644 index 0000000..f46fdfc --- /dev/null +++ b/src/Managing.Application.Workers/WorkerService.cs @@ -0,0 +1,69 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Workers.Abstractions; +using Managing.Common; +using Managing.Domain.Workers; + +namespace Managing.Application.Workers; + +public class WorkerService : IWorkerService +{ + private readonly IWorkerRepository _workerRepository; + + public WorkerService(IWorkerRepository workerRepository) + { + _workerRepository = workerRepository; + } + + public async Task GetWorker(Enums.WorkerType workerType) + { + return await _workerRepository.GetWorkerAsync(workerType); + } + + public async Task InsertWorker(Enums.WorkerType workerType, TimeSpan delay) + { + var worker = new Worker() + { + WorkerType = workerType, + StartTime = DateTime.Now, + LastRunTime = null, + ExecutionCount = 0, + Delay = delay + }; + await _workerRepository.InsertWorker(worker); + } + + public async Task UpdateWorker(Enums.WorkerType workerType, int executionCount) + { + await _workerRepository.UpdateWorker(workerType, executionCount); + } + + public async Task DisableWorker(Enums.WorkerType workerType) + { + await _workerRepository.DisableWorker(workerType); + } + + public async Task EnableWorker(Enums.WorkerType workerType) + { + await _workerRepository.EnableWorker(workerType); + } + + public IEnumerable GetWorkers() + { + return _workerRepository.GetWorkers(); + } + + public async Task ToggleWorker(Enums.WorkerType workerType) + { + var worker = await GetWorker(workerType); + if (worker.IsActive) + { + _ = _workerRepository.DisableWorker(workerType); + return false; + } + else + { + _ = _workerRepository.EnableWorker(workerType); + return true; + } + } +} diff --git a/src/Managing.Application/Abstractions/IBotFactory.cs b/src/Managing.Application/Abstractions/IBotFactory.cs new file mode 100644 index 0000000..d29fa0d --- /dev/null +++ b/src/Managing.Application/Abstractions/IBotFactory.cs @@ -0,0 +1,16 @@ +using Managing.Domain.Bots; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Workflows; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions +{ + public interface IBotFactory + { + ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); + ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); + ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); + ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); + IBot CreateSimpleBot(string botName, Workflow workflow); + } +} diff --git a/src/Managing.Application/Abstractions/ICacheService.cs b/src/Managing.Application/Abstractions/ICacheService.cs new file mode 100644 index 0000000..445b6e5 --- /dev/null +++ b/src/Managing.Application/Abstractions/ICacheService.cs @@ -0,0 +1,12 @@ +namespace Managing.Application.Abstractions +{ + public interface ICacheService + { + string SaveValue(string name, string value); + string GetValue(string key); + void RemoveValue(string key); + T GetOrSave(string name, Func action, TimeSpan slidingExpiration); + T GetValue(string key); + void SaveValue(string name, T value, TimeSpan slidingExpiration); + } +} diff --git a/src/Managing.Application/Abstractions/ICommandHandler.cs b/src/Managing.Application/Abstractions/ICommandHandler.cs new file mode 100644 index 0000000..6dbadbe --- /dev/null +++ b/src/Managing.Application/Abstractions/ICommandHandler.cs @@ -0,0 +1,7 @@ +namespace Managing.Application.Abstractions +{ + public interface ICommandHandler + { + Task Handle(T request); + } +} \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/IFlowFactory.cs b/src/Managing.Application/Abstractions/IFlowFactory.cs new file mode 100644 index 0000000..a7c6e8d --- /dev/null +++ b/src/Managing.Application/Abstractions/IFlowFactory.cs @@ -0,0 +1,9 @@ +using Managing.Domain.Workflows; +using Managing.Domain.Workflows.Synthetics; + +namespace Managing.Application.Abstractions; + +public interface IFlowFactory +{ + IFlow BuildFlow(SyntheticFlow request); +} diff --git a/src/Managing.Application/Abstractions/IMoneyManagementService.cs b/src/Managing.Application/Abstractions/IMoneyManagementService.cs new file mode 100644 index 0000000..18a4f18 --- /dev/null +++ b/src/Managing.Application/Abstractions/IMoneyManagementService.cs @@ -0,0 +1,13 @@ +using Managing.Domain.MoneyManagements; + +namespace Managing.Application.Abstractions +{ + public interface IMoneyManagementService + { + Task CreateOrUpdateMoneyManagement(MoneyManagement request); + Task GetMoneyMangement(string name); + IEnumerable GetMoneyMangements(); + bool DeleteMoneyManagement(string name); + bool DeleteMoneyManagements(); + } +} diff --git a/src/Managing.Application/Abstractions/IScenarioService.cs b/src/Managing.Application/Abstractions/IScenarioService.cs new file mode 100644 index 0000000..d9cbd1b --- /dev/null +++ b/src/Managing.Application/Abstractions/IScenarioService.cs @@ -0,0 +1,29 @@ +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions +{ + public interface IScenarioService + { + IEnumerable GetScenarios(); + Scenario CreateScenario(string name, List strategies); + IEnumerable GetStrategies(); + bool DeleteStrategy(string name); + bool DeleteScenario(string name); + Scenario GetScenario(string name); + Strategy CreateStrategy(StrategyType type, + Timeframe timeframe, + 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 DeleteStrategies(); + bool DeleteScenarios(); + } +} diff --git a/src/Managing.Application/Abstractions/ISettingsService.cs b/src/Managing.Application/Abstractions/ISettingsService.cs new file mode 100644 index 0000000..bb6a816 --- /dev/null +++ b/src/Managing.Application/Abstractions/ISettingsService.cs @@ -0,0 +1,7 @@ +namespace Managing.Application.Abstractions; + +public interface ISettingsService +{ + bool SetupSettings(); + Task ResetSettings(); +} diff --git a/src/Managing.Application/Abstractions/ITaskCache.cs b/src/Managing.Application/Abstractions/ITaskCache.cs new file mode 100644 index 0000000..07e6e5b --- /dev/null +++ b/src/Managing.Application/Abstractions/ITaskCache.cs @@ -0,0 +1,48 @@ +namespace Managing.Application.Abstractions +{ + public interface ITaskCache + { + ///

+ /// Return from the cache the value for the given key. If value is already present in cache, + /// that value will be returned. Otherwise value is first generated with the given method. + /// + /// Return value can be a completed or running task-object. If the task-object is completed, + /// it has run succesfully to completion. Most often when a running task is returned, + /// it is the task returned by the function the caller has given as a parameter, but the + /// returned task might also have a different origin (from another call to this same method). + /// If the cache contains a task that will end up throwing an exception in the future, the same + /// task instance is returned to all the callers of this method. This means that any given + /// caller of this method should anticipate the type of exceptions that could be thrown from + /// the updateFunc used by any of the caller of this method. + /// + /// To prevent the problem described above, as a convention, all the call sites of his method + /// (if more than one) should use the same updateFunc-parameter and also be prepared for the + /// exceptions that the the updateFunc could throw. + /// + /// Type of the value. + /// Key that matches the wanted return value. + /// Function that is run only if a value for the given key is not already present in the cache. + /// Returned task-object can be completed or running. Note that the task might result in exception. + Task AddOrGetExisting(string key, Func> valueFactory); + + /// + /// Invalidate the value for the given key, if value exists. + /// + /// + void Invalidate(string key); + + /// + /// Does the cache alrealy contain a value for the key. + /// + /// + /// + bool Contains(string key); + + /// + /// Empties the cache from all entries. + /// + void Clear(); + List GetCache(); + T Get(string name); + } +} diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs new file mode 100644 index 0000000..a62d70d --- /dev/null +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -0,0 +1,33 @@ +using Managing.Domain.Bots; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Abstractions +{ + public interface ITradingBot : IBot + { + HashSet Signals { get; set; } + List Positions { get; set; } + HashSet Candles { get; set; } + Timeframe Timeframe { get; set; } + HashSet Strategies { get; set; } + Ticker Ticker { get; } + string Scenario { get; } + string AccountName { get; } + bool IsForWatchingOnly { get; set; } + MoneyManagement MoneyManagement { get; set; } + BotType BotType { get; set; } + Dictionary WalletBalances { get; set; } + + + Task Run(); + Task ToggleIsForWatchOnly(); + int GetWinRate(); + decimal GetProfitAndLoss(); + decimal GetTotalFees(); + void LoadStrategies(IEnumerable strategies); + } +} diff --git a/src/Managing.Application/Abstractions/IWorkflowService.cs b/src/Managing.Application/Abstractions/IWorkflowService.cs new file mode 100644 index 0000000..18165d4 --- /dev/null +++ b/src/Managing.Application/Abstractions/IWorkflowService.cs @@ -0,0 +1,12 @@ +using Managing.Domain.Workflows; +using Managing.Domain.Workflows.Synthetics; + +namespace Managing.Application.Abstractions; + +public interface IWorkflowService +{ + bool DeleteWorkflow(string name); + Task> GetAvailableFlows(); + IEnumerable GetWorkflows(); + Task InsertOrUpdateWorkflow(SyntheticWorkflow workflowRequest); +} diff --git a/src/Managing.Application/Accounts/AccountService.cs b/src/Managing.Application/Accounts/AccountService.cs new file mode 100644 index 0000000..02e9106 --- /dev/null +++ b/src/Managing.Application/Accounts/AccountService.cs @@ -0,0 +1,172 @@ +using Managing.Application.Abstractions; +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; + +namespace Managing.Application.Accounts; +public class AccountService : IAccountService +{ + private readonly IAccountRepository _accountRepository; + private readonly IExchangeService _exchangeService; + private readonly IEvmManager _evmManager; + private readonly ICacheService _cacheService; + private readonly ILogger _logger; + + public AccountService( + IAccountRepository accountRepository, + ILogger logger, + IExchangeService exchangeService, + IEvmManager evmManager, + ICacheService cacheService) + { + _accountRepository = accountRepository; + _logger = logger; + _exchangeService = exchangeService; + _evmManager = evmManager; + _cacheService = cacheService; + } + + public async Task CreateAccount(User user, Account request) + { + var account = await _accountRepository.GetAccountByNameAsync(request.Name); + + if (account != null) + { + throw new Exception($"Account {request.Name} alreary exist"); + } + else + { + request.User = user; + + if (request.Exchange == Enums.TradingExchanges.Evm + && request.Type == Enums.AccountType.Trader) + { + var keys = _evmManager.GenerateAddress(); + request.Key = keys.Key; + request.Secret = keys.Secret; + } + else + { + request.Key = request.Key; + request.Secret = request.Secret; + } + + await _accountRepository.InsertAccountAsync(request); + } + + return request; + } + + public bool DeleteAccount(User user, string name) + { + try + { + _accountRepository.DeleteAccountByName(name); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + + public async Task GetAccount(string name, bool hideSecrets, bool getBalance) + { + var account = await _accountRepository.GetAccountByNameAsync(name); + ManageProperties(hideSecrets, getBalance, account); + + return account; + } + + public async Task GetAccountByKey(string key, bool hideSecrets, bool getBalance) + { + var account = await _accountRepository.GetAccountByKeyAsync(key); + ManageProperties(hideSecrets, getBalance, account); + + return account; + } + + public async Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance) + { + var account = await _accountRepository.GetAccountByNameAsync(name); + ManageProperties(hideSecrets, getBalance, account); + + return account; + } + + public IEnumerable GetAccounts(bool hideSecrets, bool getBalance) + { + var result = _accountRepository.GetAccounts(); + var accounts = new List(); + + foreach (var account in result) + { + ManageProperties(hideSecrets, getBalance, account); + accounts.Add(account); + } + + return accounts; + } + + public IEnumerable GetAccountsByUser(User user, bool hideSecrets = true) + { + var cacheKey = $"user-account-{user.Name}"; + + return _cacheService.GetOrSave(cacheKey, () => + { + return GetAccounts(user, hideSecrets, false); + }, TimeSpan.FromMinutes(5)); + } + + private IEnumerable GetAccounts(User user, bool hideSecrets, bool getBalance) + { + var result = _accountRepository.GetAccounts(); + var accounts = new List(); + + foreach (var account in result.Where(a => a.User.Name == user.Name)) + { + ManageProperties(hideSecrets, getBalance, account); + accounts.Add(account); + } + + return accounts; + } + + public IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets) + { + var cacheKey = $"user-account-balance-{user.Name}"; + var accounts = _cacheService.GetOrSave(cacheKey, () => + { + return GetAccounts(user, true, true); + }, TimeSpan.FromHours(3)); + + return accounts; + } + + private void ManageProperties(bool hideSecrets, bool getBalance, Account account) + { + if (account != null) + { + if (getBalance) + { + try + { + account.Balances = _exchangeService.GetBalances(account).Result; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Cannot get account {account.Name} balance"); + } + } + + if (hideSecrets) + { + account.Secret = ""; + } + } + } +} \ No newline at end of file diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs new file mode 100644 index 0000000..57bb463 --- /dev/null +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -0,0 +1,192 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Core; +using Managing.Domain.Accounts; +using Managing.Domain.Backtests; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Managing.Domain.Shared.Helpers; +using Managing.Domain.Workflows; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Application.Backtesting +{ + public class Backtester : IBacktester + { + private readonly IBacktestRepository _backtestRepository; + private readonly ILogger _logger; + private readonly IExchangeService _exchangeService; + private readonly IBotFactory _botFactory; + + public Backtester( + IExchangeService exchangeService, + IBotFactory botFactory, + IBacktestRepository backtestRepository, + ILogger logger) + { + _exchangeService = exchangeService; + _botFactory = botFactory; + _backtestRepository = backtestRepository; + _logger = logger; + } + + public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false) + { + var simplebot = _botFactory.CreateSimpleBot("scenario", workflow); + Backtest result = null; + if (save) + { + _backtestRepository.InsertBacktest(result); + } + return result; + } + + public Backtest RunScalpingBotBacktest( + Account account, + MoneyManagement moneyManagement, + Ticker ticker, + Scenario scenario, + Timeframe timeframe, + double days, + decimal balance, + bool isForWatchingOnly = false, + bool save = false) + { + var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", timeframe, isForWatchingOnly); + scalpingBot.LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario)); + var candles = GetCandles(account, ticker, timeframe, days); + var result = GetBacktestingResult(ticker, scenario, timeframe, scalpingBot, candles, balance, account, moneyManagement); + if (save) + { + _backtestRepository.InsertBacktest(result); + } + return result; + } + + private List GetCandles(Account account, Ticker ticker, Timeframe timeframe, double days) + { + var candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result; + + if (candles == null || candles.Count == 0) + throw new Exception($"No candles for {ticker} on {account.Exchange}"); + + 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) + { + var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", timeframe, false); + var strategy = ScenarioHelpers.GetStrategiesFromScenario(scenario); + flippingBot.LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario)); + var candles = GetCandles(account, ticker, timeframe, days); + var result = GetBacktestingResult(ticker, scenario, timeframe, flippingBot, candles, balance, account, moneyManagement); + if (save) + { + _backtestRepository.InsertBacktest(result); + } + return result; + } + + public Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List candles, decimal balance) + { + var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); + var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", timeframe, false); + bot.LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario)); + var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account, moneyManagement); + return result; + } + + public Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List candles, decimal balance) + { + var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); + var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", timeframe, false); + var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account, moneyManagement); + return result; + } + + private static Backtest GetBacktestingResult( + Ticker ticker, + Scenario scenario, + Timeframe timeframe, + ITradingBot bot, + List candles, + decimal balance, + Account account, + MoneyManagement moneyManagement) + { + if (candles == null || candles.Count == 0) + { + throw new Exception("No candle to backtest"); + } + + var hodlBalances = new Dictionary(); + bot.WalletBalances.Add(candles.FirstOrDefault().Date, balance); + foreach (var candle in candles) + { + hodlBalances.Add(candle.Date, candle.Close); + bot.Candles.Add(candle); + bot.Run(); + } + + var finalPnl = bot.GetProfitAndLoss(); + var winRate = bot.GetWinRate(); + var optimizedMoneyManagement = TradingBox.GetBestMoneyManagement(candles, bot.Positions, moneyManagement); + var stats = TradingHelpers.GetStatistics(bot.WalletBalances); + var growthPercentage = TradingHelpers.GetGrowthFromInitalBalance(balance, finalPnl); + var hodlPercentage = TradingHelpers.GetHodlPercentage(candles[0], candles.Last()); + var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles, bot.BotType, account.Name) + { + FinalPnl = finalPnl, + WinRate = winRate, + GrowthPercentage = growthPercentage, + HodlPercentage = hodlPercentage, + Fees = bot.GetTotalFees(), + WalletBalances = bot.WalletBalances.ToList(), + Statistics = stats, + OptimizedMoneyManagement = optimizedMoneyManagement, + MoneyManagement = moneyManagement + }; + + return result; + } + + + + public IEnumerable GetBacktests() + { + return _backtestRepository.GetBacktests(); + } + + public bool DeleteBacktest(string id) + { + try + { + _backtestRepository.DeleteBacktestById(id); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + + public bool DeleteBacktests() + { + try + { + _backtestRepository.DeleteAllBacktests(); + //_backtestRepository.DropCollection(); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + } +} diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs new file mode 100644 index 0000000..539f3f5 --- /dev/null +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Workflows; +using Managing.Domain.Bots; + +namespace Managing.Application.Bots.Base +{ + public class BotFactory : IBotFactory + { + private readonly IMoneyManagementService _moneyManagementService; + private readonly IExchangeService _exchangeService; + private readonly IMessengerService _messengerService; + private readonly IAccountService _accountService; + private readonly ILogger _tradingBotLogger; + private readonly ITradingService _tradingService; + + public BotFactory( + IExchangeService exchangeService, + ILogger tradingBotLogger, + IMoneyManagementService moneyManagementService, + IMessengerService messengerService, + IAccountService accountService, + ITradingService tradingService) + { + _tradingBotLogger = tradingBotLogger; + _exchangeService = exchangeService; + _moneyManagementService = moneyManagementService; + _messengerService = messengerService; + _accountService = accountService; + _tradingService = tradingService; + } + + IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow) + { + return new SimpleBot(botName, _tradingBotLogger, workflow); + } + + ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + { + return new ScalpingBot( + accountName, + moneyManagement, + name, + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + isForWatchingOnly: isForWatchingOnly); + } + + ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + { + return new ScalpingBot( + accountName, + moneyManagement, + "BacktestBot", + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + true, + isForWatchingOnly); + } + + public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + { + return new FlippingBot( + accountName, + moneyManagement, + name, + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + isForWatchingOnly: isForWatchingOnly); + } + + public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + { + return new FlippingBot( + accountName, + moneyManagement, + "BacktestBot", + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + true, + isForWatchingOnly); + } + } +} diff --git a/src/Managing.Application/Bots/FlippingBot.cs b/src/Managing.Application/Bots/FlippingBot.cs new file mode 100644 index 0000000..058a910 --- /dev/null +++ b/src/Managing.Application/Bots/FlippingBot.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; +using Managing.Application.Abstractions.Services; +using Managing.Domain.MoneyManagements; + +namespace Managing.Application.Bots +{ + public class FlippingBot : TradingBot + { + public FlippingBot(string accountName, + MoneyManagement moneyManagement, + string name, + string scenario, + IExchangeService exchangeService, + Ticker ticker, + ITradingService tradingService, + ILogger logger, + Timeframe timeframe, + IAccountService accountService, + IMessengerService messengerService, + bool isForBacktest = false, + bool isForWatchingOnly = false) + : base(accountName, + moneyManagement, + name, + ticker, + scenario, + exchangeService, + logger, + tradingService, + timeframe, + accountService, + messengerService, + isForBacktest, + isForWatchingOnly, + flipPosition: true) + { + BotType = BotType.FlippingBot; + Start(); + } + + public sealed override void Start() + { + Logger.LogInformation($"{Name} - Bot Started"); + base.Start(); + Logger.LogInformation($"Starting {Name} bot - Status : {Status}"); + } + } +} diff --git a/src/Managing.Application/Bots/ScalpingBot.cs b/src/Managing.Application/Bots/ScalpingBot.cs new file mode 100644 index 0000000..f770c15 --- /dev/null +++ b/src/Managing.Application/Bots/ScalpingBot.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; +using Managing.Application.Abstractions.Services; +using Managing.Domain.MoneyManagements; + +namespace Managing.Application.Bots +{ + public class ScalpingBot : TradingBot + { + public ScalpingBot(string accountName, + MoneyManagement moneyManagement, + string name, + string scenario, + IExchangeService exchangeService, + Ticker ticker, + ITradingService tradingService, + ILogger logger, + Timeframe timeframe, + IAccountService accountService, + IMessengerService messengerService, + bool isForBacktest = false, + bool isForWatchingOnly = false) + : base(accountName, + moneyManagement, + name, + ticker, + scenario, + exchangeService, + logger, + tradingService, + timeframe, + accountService, + messengerService, + isForBacktest, + isForWatchingOnly) + { + BotType = BotType.ScalpingBot; + Start(); + } + + public sealed override void Start() + { + Logger.LogInformation($"{Name} - Bot Started"); + base.Start(); + Logger.LogInformation($"Starting {Name} bot - Status : {Status}"); + } + } +} diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs new file mode 100644 index 0000000..ee8feb8 --- /dev/null +++ b/src/Managing.Application/Bots/SimpleBot.cs @@ -0,0 +1,39 @@ +using Managing.Domain.Bots; +using Managing.Domain.Workflows; +using Microsoft.Extensions.Logging; + +namespace Managing.Application.Bots +{ + public class SimpleBot : Bot + { + public readonly ILogger Logger; + private readonly Workflow _workflow; + + public SimpleBot(string name, ILogger logger, Workflow workflow) : base(name) + { + Logger = logger; + _workflow = workflow; + Interval = 100; + Start(); + } + + public override void Start() + { + Task.Run(() => InitWorker(Run)); + + base.Start(); + } + + public async Task Run() + { + await Task.Run( + async () => + { + Logger.LogInformation(Identifier); + Logger.LogInformation(DateTime.Now.ToString()); + await _workflow.Execute(); + Logger.LogInformation("__________________________________________________"); + }); + } + } +} diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs new file mode 100644 index 0000000..1dde4ab --- /dev/null +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -0,0 +1,651 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading; +using Managing.Application.Trading.Commands; +using Managing.Domain.Accounts; +using Managing.Domain.Bots; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Managing.Domain.Shared.Helpers; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Application.Bots; + +public class TradingBot : Bot, ITradingBot +{ + public readonly ILogger Logger; + public readonly IExchangeService ExchangeService; + public readonly IMessengerService MessengerService; + public readonly IAccountService AccountService; + private readonly ITradingService TradingService; + + public Account Account { get; set; } + public HashSet Strategies { get; set; } + public HashSet Candles { get; set; } + public HashSet Signals { get; set; } + public List Positions { get; set; } + public Ticker Ticker { get; set; } + public string Scenario { get; set; } + public string AccountName { get; set; } + public MoneyManagement MoneyManagement { get; set; } + public Timeframe Timeframe { get; set; } + public bool IsForBacktest { get; set; } + public DateTime PreloadSince { get; set; } + public bool IsForWatchingOnly { get; set; } + public bool FlipPosition { get; set; } + public int PreloadedCandlesCount { get; set; } + public BotType BotType { get; set; } + public decimal Fee { get; set; } + public Dictionary WalletBalances { get; set; } + + public TradingBot( + string accountName, + MoneyManagement moneyManagement, + string name, + Ticker ticker, + string scenario, + IExchangeService exchangeService, + ILogger logger, + ITradingService tradingService, + Timeframe timeframe, + IAccountService accountService, + IMessengerService messengerService, + bool isForBacktest = false, + bool isForWatchingOnly = false, + bool flipPosition = false) + : base(name) + { + ExchangeService = exchangeService; + AccountService = accountService; + MessengerService = messengerService; + TradingService = tradingService; + + IsForWatchingOnly = isForWatchingOnly; + FlipPosition = flipPosition; + AccountName = accountName; + MoneyManagement = moneyManagement; + Ticker = ticker; + Scenario = scenario; + Timeframe = timeframe; + IsForBacktest = isForBacktest; + Logger = logger; + + Strategies = new HashSet(); + Signals = new HashSet(); + Candles = new HashSet(); + Positions = new List(); + WalletBalances = new Dictionary(); + + if (!isForBacktest) + { + Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe); + PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe); + } + } + + public override async void Start() + { + base.Start(); + await LoadAccount(); + + if (!IsForBacktest) + { + LoadScenario(); + await PreloadCandles(); + await CancelAllOrders(); + await MessengerService.SendMessage($"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies."); + await InitWorker(Run); + } + + Fee = TradingService.GetFee(Account, IsForBacktest); + } + + private async Task LoadAccount() + { + var account = await AccountService.GetAccount(AccountName, false, true); + if (account == null) + { + Logger.LogWarning($"No account found for this {AccountName}"); + Stop(); + } + else + { + Account = account; + } + } + + public void LoadScenario() + { + var scenario = TradingService.GetScenarioByName(Scenario); + if (scenario == null) + { + Logger.LogWarning("No scenario found for this scenario name"); + Stop(); + } + else + { + LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario)); + } + } + + public void LoadStrategies(IEnumerable strategies) + { + foreach (var strategy in strategies) + { + Strategies.Add(strategy); + } + } + + public async Task Run() + { + Logger.LogInformation($"____________________{Name}____________________"); + Logger.LogInformation($"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}"); + + var previousCandleCount = Candles.Count; + + if (!IsForBacktest) + await UpdateCandles(); + + if (Candles.Count > previousCandleCount || IsForBacktest) + await UpdateSignals(Candles); + else + Logger.LogInformation($"No need to update signals for {Ticker}"); + + if (!IsForWatchingOnly) + await ManagePositions(); + + await UpdateWalletBalances(); + Logger.LogInformation($"Candles : {Candles.Count}"); + Logger.LogInformation($"Signals : {Signals.Count}"); + Logger.LogInformation($"ExecutionCount : {ExecutionCount}"); + Logger.LogInformation($"Positions : {Positions.Count}"); + Logger.LogInformation("__________________________________________________"); + } + + private async Task PreloadCandles() + { + var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe); + + foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + { + if (!Candles.Any(c => c.Date == candle.Date)) + { + Candles.Add(candle); + await UpdateSignals(Candles); + } + } + + PreloadedCandlesCount = Candles.Count(); + } + + private async Task UpdateSignals(HashSet candles) + { + var signal = TradingBox.GetSignal(candles, Strategies, Signals); + + if (signal == null) return; + + await AddSignal(signal); + } + + + private async Task AddSignal(Signal signal) + { + Signals.Add(signal); + + if (!IsForBacktest) + TradingService.InsertSignal(signal); + + if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest)) + signal.Status = SignalStatus.Expired; + + var signalText = $"{Scenario} trigger a signal. Signal told you " + + $"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; + + Logger.LogInformation(signalText); + + if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0) + { + await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe); + } + } + + protected async Task UpdateCandles() + { + if (Candles.Count == 0 || ExecutionCount == 0) + return; + + var lastCandle = Candles.Last(); + var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe); + + foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime())) + { + Candles.Add(candle); + } + } + + private async Task ManagePositions() + { + if (!IsForBacktest && ExecutionCount < 1) + return; + + // Update position + foreach (var signal in Signals.Where(s => s.Status == SignalStatus.PositionOpen)) + { + var positionForSignal = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier); + await UpdatePosition(signal, positionForSignal); + } + + // Open position for signal waiting for a position open + foreach (var signal in Signals.Where(s => s.Status == SignalStatus.WaitingForPosition)) + { + Task.Run(() => OpenPosition(signal)).GetAwaiter().GetResult(); + } + } + + private async Task UpdateWalletBalances() + { + if (WalletBalances.Count == 0) + { + WalletBalances.Add(Candles.LastOrDefault().Date, await ExchangeService.GetBalance(Account, IsForBacktest)); + } + else if (!WalletBalances.Any(w => w.Key == Candles.LastOrDefault().Date)) + { + var walletBalance = WalletBalances.FirstOrDefault().Value + GetProfitAndLoss(); + WalletBalances.Add(Candles.LastOrDefault().Date, walletBalance); + } + } + + private async Task UpdatePosition(Signal signal, Position positionForSignal) + { + try + { + Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}"); + + var position = IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier); + + if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped)) + { + await HandleClosedPosition(positionForSignal); + return; + } + else if (position.Status == PositionStatus.Filled) + { + // For backtesting or force close if not executed on exchange : + // check if position is still open + // Check status, if still open update the status of the position + var lastCandle = IsForBacktest ? Candles.Last() : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); + + if (positionForSignal.OriginDirection == TradeDirection.Long) + { + if (positionForSignal.StopLoss.Price >= lastCandle.Low) + { + await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true); + positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); + } + else if (positionForSignal.TakeProfit1.Price <= lastCandle.High + && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) + { + await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); + positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); + } + else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High) + { + await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true); + positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); + } + else + { + Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened"); + } + } + + if (positionForSignal.OriginDirection == TradeDirection.Short) + { + if (positionForSignal.StopLoss.Price <= lastCandle.High) + { + await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true); + positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); + } + else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low + && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) + { + await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); + positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); + } + else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low) + { + await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true); + positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); + } + else + { + Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened"); + } + } + } + else if (position.Status == (PositionStatus.Rejected | PositionStatus.Canceled)) + { + await LogWarning($"Open position trade is rejected for signal {signal.Identifier}"); + // if position is not open + // Re-open the trade for the signal only if signal still up + //if (signal.Status == SignalStatus.PositionOpen) + //{ + // Logger.LogInformation($"Try to re-open position"); + // OpenPosition(signal); + //} + } + } + catch (Exception ex) + { + Logger.LogError(ex, ex.Message); + return; + } + } + + + + private async Task OpenPosition(Signal signal) + { + // Check if a position is already open + Logger.LogInformation($"Opening position for {signal.Identifier}"); + + var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled + && p.SignalIdentifier != signal.Identifier); + + var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow); + + // If position open + if (openedPosition != null) + { + var previousSignal = Signals.First(s => s.Identifier == openedPosition.SignalIdentifier); + + // Check if signal is the opposite side => flip the position + if (openedPosition.OriginDirection == signal.Direction) + { + // An operation is already open for the same direction + await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction"); + SetSignalStatus(signal.Identifier, SignalStatus.Expired); + } + else + { + // An operation is already open for the opposite direction + // ==> Flip the position + if (FlipPosition) + { + await LogInformation("Try to flip the position because of an opposite direction signal"); + await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); + await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped); + await OpenPosition(signal); + await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$"); + } + else + { + await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped."); + SetSignalStatus(signal.Identifier, SignalStatus.Expired); + } + } + } + else + { + if (!CanOpenPosition(signal)) + { + await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); + SetSignalStatus(signal.Identifier, SignalStatus.Expired); + return; + } + + await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}"); + + try + { + var command = new OpenPositionRequest( + AccountName, + MoneyManagement, + signal.Direction, + Ticker, + PositionInitiator.Bot, + signal.Date, + IsForBacktest, + lastPrice, + balance: WalletBalances.LastOrDefault().Value, + fee: Fee); + + var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService) + .Handle(command); + + if (position != null) + { + if (position.Open.Status != TradeStatus.Cancelled) + { + position.SignalIdentifier = signal.Identifier; + Positions.Add(position); + SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); + + if (!IsForBacktest) + { + await MessengerService.SendPosition(position); + } + + Logger.LogInformation($"Position requested"); + } + else + { + await SetPositionStatus(signal.Identifier, PositionStatus.Rejected); + SetSignalStatus(signal.Identifier, SignalStatus.Expired); + } + } + } + catch (Exception ex) + { + SetSignalStatus(signal.Identifier, SignalStatus.Expired); + await LogWarning($"Cannot open trade : {ex.Message}"); + } + } + } + + private bool CanOpenPosition(Signal signal) + { + if (Positions.Count == 0) + return true; + + var lastPosition = Positions.LastOrDefault(p => p.IsFinished() + && p.SignalIdentifier != signal.Identifier + && p.ProfitAndLoss.Realized < 0 + && p.OriginDirection == signal.Direction); + + if (lastPosition == null) + return true; + + var tenCandleAgo = Candles.TakeLast(10).First(); + var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier); + + return positionSignal.Date < tenCandleAgo.Date; + } + + private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false) + { + if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && tradeToClose.TradeType == TradeType.StopMarket) + { + // If trade is the 2nd Take profit + tradeToClose.Quantity = position.TakeProfit2.Quantity; + } + + await LogInformation($"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " + + $"- Closing Position : {tradeClosingPosition}"); + + // Get status of position before closing it. The position might be already close by the exchange + if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0) + { + Logger.LogInformation($"Trade already close on exchange"); + await HandleClosedPosition(position); + } + else + { + var command = new ClosePositionCommand(position, lastPrice); + try + { + var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)) + .Handle(command); + + if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped)) + { + if (tradeClosingPosition) + { + await SetPositionStatus(signal.Identifier, PositionStatus.Finished); + } + + await HandleClosedPosition(closedPosition); + } + else + { + throw new Exception($"Wrong position status : {closedPosition.Status}"); + } + } + catch (Exception ex) + { + await LogWarning($"Position {signal.Identifier} not closed : {ex.Message}"); + + if (position.Status == (PositionStatus.Canceled | PositionStatus.Rejected)) + { + // Trade close on exchange => Should close trade manually + await SetPositionStatus(signal.Identifier, PositionStatus.Finished); + } + } + + } + } + + private async Task HandleClosedPosition(Position position) + { + if (Positions.Any(p => p.Identifier == position.Identifier)) + { + var previousPosition = Positions.First(p => p.Identifier == position.Identifier); + var positionIndex = Positions.IndexOf(previousPosition); + position.SignalIdentifier = previousPosition.SignalIdentifier; + Positions[positionIndex] = position; + SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired); + Logger.LogInformation($"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}"); + } + else + { + await LogWarning("Weird things happen - Trying to update position status, but no position found"); + } + + if (!IsForBacktest) + { + await MessengerService.SendClosingPosition(position); + } + + await CancelAllOrders(); + } + + private async Task CancelAllOrders() + { + if (!IsForBacktest) + { + try + { + var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker); + Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}"); + } + catch (Exception ex) + { + // Todo handle exception from evm + Logger.LogError(ex, "Error during cancelOrders"); + } + } + } + + private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus) + { + await LogInformation($"Position {signalIdentifier} is now {positionStatus}"); + Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus; + SetSignalStatus(signalIdentifier, SignalStatus.Expired); + } + + private void SetSignalStatus(string signalIdentifier, SignalStatus signalStatus) + { + if (Signals.Any(s => s.Identifier == signalIdentifier)) + { + Signals.First(s => s.Identifier == signalIdentifier).Status = signalStatus; + Logger.LogInformation($"Signal {signalIdentifier} is now {signalStatus}"); + } + } + + public int GetWinRate() + { + var succeededPositions = Positions.Where(p => p.IsFinished()).Count(p => p.ProfitAndLoss?.Realized > 0); + var total = Positions.Where(p => p.IsFinished()).Count(); + + if (total == 0) + return 0; + + return (succeededPositions * 100) / total; + } + + public decimal GetProfitAndLoss() + { + var pnl = Positions.Where(p => p.ProfitAndLoss != null).Sum(p => p.ProfitAndLoss.Realized); + return pnl - GetTotalFees(); + } + + /// + /// Calculates the total fees paid by the trading bot for each position. + /// + /// Returns the total fees paid as a decimal value. + public decimal GetTotalFees() + { + decimal fees = 0; + foreach (var position in Positions.Where(p => p.Open.Fee > 0)) + { + fees += position.Open.Fee; + fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0; + fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0; + + if (position.IsFinished() && + position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled) + fees += position.Open.Fee; + + if (position.TakeProfit2 != null) + fees += position.TakeProfit2.Fee; + } + + return fees; + } + + public async Task ToggleIsForWatchOnly() + { + IsForWatchingOnly = (!IsForWatchingOnly); + await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}"); + } + + private async Task LogInformation(string message) + { + Logger.LogInformation(message); + await SendTradeMessage(message); + } + + private async Task LogWarning(string message) + { + Logger.LogWarning(message); + await SendTradeMessage(message, true); + } + + private async Task SendTradeMessage(string message, bool isBadBehavior = false) + { + if (!IsForBacktest) + { + await MessengerService.SendTradeMessage(message, isBadBehavior); + } + } +} diff --git a/src/Managing.Application/Hubs/BacktestHub.cs b/src/Managing.Application/Hubs/BacktestHub.cs new file mode 100644 index 0000000..0b29eb1 --- /dev/null +++ b/src/Managing.Application/Hubs/BacktestHub.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.SignalR; + +namespace Managing.Application.Hubs; + +public class BacktestHub : Hub +{ + public async override Task OnConnectedAsync() + { + await base.OnConnectedAsync(); + await Clients.Caller.SendAsync("Message", $"Connected successfully on backtest hub. ConnectionId : {Context.ConnectionId}"); + } + + public async Task SubscribeBots() => + await Clients.All.SendAsync("BacktestsSubscription", "Successfully subscribed"); +} diff --git a/src/Managing.Application/Hubs/BotHub.cs b/src/Managing.Application/Hubs/BotHub.cs new file mode 100644 index 0000000..a85ad6b --- /dev/null +++ b/src/Managing.Application/Hubs/BotHub.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.SignalR; + +namespace Managing.Application.Hubs; + +public class BotHub : Hub +{ + public async override Task OnConnectedAsync() + { + await base.OnConnectedAsync(); + await Clients.Caller.SendAsync("Message", "Connected successfully!"); + } + + public async Task SubscribeBots() => + await Clients.All.SendAsync("BotsSubscription", "Successfully subscribed"); + + public string GetConnectionId() => Context.ConnectionId; +} \ No newline at end of file diff --git a/src/Managing.Application/Hubs/CandleHub.cs b/src/Managing.Application/Hubs/CandleHub.cs new file mode 100644 index 0000000..83ffd3a --- /dev/null +++ b/src/Managing.Application/Hubs/CandleHub.cs @@ -0,0 +1,41 @@ +using Managing.Application.Abstractions.Services; +using Microsoft.AspNetCore.SignalR; + +namespace Managing.Application.Hubs; + +public class CandleHub : Hub +{ + private int ConnectionCount = 0; + private readonly IStreamService _streamService; + + public CandleHub(IStreamService streamService) + { + _streamService = streamService; + } + + public async override Task OnConnectedAsync() + { + ConnectionCount++; + + await Clients.Caller.SendAsync("Message", $"Connected successfully on candle hub. ConnectionId : {Context.ConnectionId}"); + + //await _streamService.SubscribeCandle(async (candle) => { + // await Clients.All.SendAsync("Candle", candle); + //}); + await _streamService.SubscribeCandle(); + await base.OnConnectedAsync(); + + } + + public override async Task OnDisconnectedAsync(Exception ex) + { + await Clients.Caller.SendAsync("Message", $"Shuting down candle hub. ConnectionId : {Context.ConnectionId}"); + + ConnectionCount--; + if(ConnectionCount == 0) + { + await _streamService.UnSubscribeCandle(); + } + await base.OnDisconnectedAsync(ex); + } +} diff --git a/src/Managing.Application/Hubs/PositionHub.cs b/src/Managing.Application/Hubs/PositionHub.cs new file mode 100644 index 0000000..c3ea49e --- /dev/null +++ b/src/Managing.Application/Hubs/PositionHub.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.SignalR; + +namespace Managing.Application.Hubs; + +public class PositionHub : Hub +{ + public async override Task OnConnectedAsync() + { + await base.OnConnectedAsync(); + await Clients.Caller.SendAsync("Message", $"Connected successfully on position hub. ConnectionId : {Context.ConnectionId}"); + } +} diff --git a/src/Managing.Application/ManageBot/Commands/DeleteBotCommand.cs b/src/Managing.Application/ManageBot/Commands/DeleteBotCommand.cs new file mode 100644 index 0000000..60ecaf8 --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/DeleteBotCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace Managing.Application.ManageBot.Commands; + +public class DeleteBotCommand : IRequest +{ + public string Name { get; } + + public DeleteBotCommand(string name) + { + Name = name; + } +} diff --git a/src/Managing.Application/ManageBot/Commands/GetActiveBotsCommand.cs b/src/Managing.Application/ManageBot/Commands/GetActiveBotsCommand.cs new file mode 100644 index 0000000..ce89a51 --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/GetActiveBotsCommand.cs @@ -0,0 +1,12 @@ +using Managing.Application.Abstractions; +using MediatR; + +namespace Managing.Application.ManageBot.Commands +{ + public class GetActiveBotsCommand : IRequest> + { + public GetActiveBotsCommand() + { + } + } +} diff --git a/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs b/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs new file mode 100644 index 0000000..40ee74a --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace Managing.Application.ManageBot.Commands +{ + public class ToggleIsForWatchingCommand : IRequest + { + public string Name { get; } + + public ToggleIsForWatchingCommand(string name) + { + Name = name; + } + } +} diff --git a/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs b/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs new file mode 100644 index 0000000..90c7833 --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs @@ -0,0 +1,36 @@ +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.ManageBot.Commands +{ + public class StartBotCommand : IRequest + { + public string Name { get; } + public BotType BotType { get; } + public Ticker Ticker { get; internal set; } + public Timeframe Timeframe { get; internal set; } + public bool IsForWatchingOnly { get; internal set; } + public string Scenario { get; internal set; } + public string AccountName { get; internal set; } + public string MoneyManagementName { get; internal set; } + + public StartBotCommand(BotType botType, + string name, + Ticker ticker, + string scenario, + Timeframe timeframe, + string accountName, + string moneyManagementName, + bool isForWatchingOnly = false) + { + BotType = botType; + Name = name; + Scenario = scenario; + Ticker = ticker; + Timeframe = timeframe; + IsForWatchingOnly = isForWatchingOnly; + AccountName = accountName; + MoneyManagementName = moneyManagementName; + } + } +} diff --git a/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs b/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs new file mode 100644 index 0000000..d0132b1 --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/StopBotCommand.cs @@ -0,0 +1,17 @@ +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.ManageBot.Commands +{ + public class StopBotCommand : IRequest + { + public string Name { get; } + public BotType BotType { get; } + + public StopBotCommand(BotType botType, string name) + { + BotType = botType; + Name = name; + } + } +} diff --git a/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs b/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs new file mode 100644 index 0000000..d92a34a --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs @@ -0,0 +1,17 @@ +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.ManageBot.Commands +{ + public class RestartBotCommand : IRequest + { + public string Name { get; } + public BotType BotType { get; } + + public RestartBotCommand(BotType botType, string name) + { + BotType = botType; + Name = name; + } + } +} diff --git a/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs b/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs new file mode 100644 index 0000000..36c4757 --- /dev/null +++ b/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs @@ -0,0 +1,32 @@ +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Managing.Application.ManageBot; + +public class DeleteBotCommandHandler : IRequestHandler +{ + private readonly ILogger _log; + private readonly ITaskCache _taskCache; + + public DeleteBotCommandHandler(ITaskCache taskCache, ILogger log) + { + _taskCache = taskCache; + _log = log; + } + + public Task Handle(DeleteBotCommand request, CancellationToken cancellationToken) + { + try + { + _taskCache.Invalidate(request.Name); + return Task.FromResult(true); + } + catch (Exception e) + { + _log.LogError(e.Message); + return Task.FromResult(false); + } + } +} diff --git a/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs b/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs new file mode 100644 index 0000000..4ee8e46 --- /dev/null +++ b/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs @@ -0,0 +1,30 @@ +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; +using Managing.Core; +using MediatR; + +namespace Managing.Application.ManageBot +{ + public class GetActiveBotsCommandHandler : IRequestHandler> + { + private readonly ITaskCache taskCache; + + public GetActiveBotsCommandHandler(ITaskCache taskCache) + { + this.taskCache = taskCache; + } + + public Task> Handle(GetActiveBotsCommand request, CancellationToken cancellationToken) + { + var cachedTask = taskCache.GetCache>(); + var result = new List(); + + foreach (var item in cachedTask) + { + result.Add(item.Value.Result); + } + + return Task.FromResult(result); + } + } +} diff --git a/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs b/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs new file mode 100644 index 0000000..b8a1079 --- /dev/null +++ b/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs @@ -0,0 +1,39 @@ +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; +using Managing.Domain.Bots; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.ManageBot +{ + public class RestartBotCommandHandler : IRequestHandler + { + private readonly ITaskCache _taskCache; + + public RestartBotCommandHandler(ITaskCache taskCache) + { + _taskCache = taskCache; + } + + public Task Handle(RestartBotCommand request, CancellationToken cancellationToken) + { + switch (request.BotType) + { + case BotType.SimpleBot: + var simpleBot = _taskCache.Get(request.Name); + simpleBot.Restart(); + return Task.FromResult(simpleBot.GetStatus()); + case BotType.ScalpingBot: + var scalpingBot = _taskCache.Get(request.Name); + scalpingBot.Restart(); + return Task.FromResult(scalpingBot.GetStatus()); + case BotType.FlippingBot: + var flippingBot = _taskCache.Get(request.Name); + flippingBot.Restart(); + return Task.FromResult(flippingBot.GetStatus()); + default: + return Task.FromResult(BotStatus.Down.ToString()); + } + } + } +} diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs new file mode 100644 index 0000000..7828678 --- /dev/null +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -0,0 +1,42 @@ +using Managing.Domain.Bots; +using MediatR; +using static Managing.Common.Enums; +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; + +namespace Managing.Application.ManageBot +{ + public class StartBotCommandHandler : IRequestHandler + { + private readonly IBotFactory _botFactory; + private readonly ITaskCache _taskCache; + private readonly IMoneyManagementService _moneyManagementService; + + public StartBotCommandHandler(IBotFactory botFactory, ITaskCache taskCache, IMoneyManagementService moneyManagementService) + { + _botFactory = botFactory; + _taskCache = taskCache; + _moneyManagementService = moneyManagementService; + } + + public Task Handle(StartBotCommand request, CancellationToken cancellationToken) + { + BotStatus botStatus = BotStatus.Down; + var moneyManagement = _moneyManagementService.GetMoneyMangement(request.MoneyManagementName).Result; + switch (request.BotType) + { + case BotType.SimpleBot: + Func> simpleBot = () => Task.FromResult(_botFactory.CreateSimpleBot(request.Name, null)); + return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, simpleBot).Result.GetStatus()); + case BotType.ScalpingBot: + Func> scalpingBot = () => Task.FromResult(_botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly)); + return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, scalpingBot).Result.GetStatus()); + case BotType.FlippingBot: + Func> flippingBot = () => Task.FromResult(_botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly)); + return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, flippingBot).Result.GetStatus()); + }; + + return Task.FromResult(botStatus.ToString()); + } + } +} diff --git a/src/Managing.Application/ManageBot/StopBotCommandHandler.cs b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs new file mode 100644 index 0000000..5a0a701 --- /dev/null +++ b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs @@ -0,0 +1,39 @@ +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; +using Managing.Domain.Bots; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.ManageBot +{ + public class StopBotCommandHandler : IRequestHandler + { + private readonly ITaskCache _taskCache; + + public StopBotCommandHandler(ITaskCache taskCache) + { + _taskCache = taskCache; + } + + public Task Handle(StopBotCommand request, CancellationToken cancellationToken) + { + switch (request.BotType) + { + case BotType.SimpleBot: + var simpleBot = _taskCache.Get(request.Name); + simpleBot.Stop(); + return Task.FromResult(simpleBot.GetStatus()); + case BotType.ScalpingBot: + var scalpingBot = _taskCache.Get(request.Name); + scalpingBot.Stop(); + return Task.FromResult(scalpingBot.GetStatus()); + case BotType.FlippingBot: + var flippingBot = _taskCache.Get(request.Name); + flippingBot.Stop(); + return Task.FromResult(flippingBot.GetStatus()); + default: + return Task.FromResult(BotStatus.Down.ToString()); + } + } + } +} diff --git a/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs b/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs new file mode 100644 index 0000000..d8ae52e --- /dev/null +++ b/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs @@ -0,0 +1,23 @@ +using Managing.Application.Abstractions; +using Managing.Application.ManageBot.Commands; +using MediatR; + +namespace Managing.Application.ManageBot +{ + public class ToggleIsForWatchingCommandHandler : IRequestHandler + { + private readonly ITaskCache _taskCache; + + public ToggleIsForWatchingCommandHandler(ITaskCache taskCache) + { + _taskCache = taskCache; + } + + public async Task Handle(ToggleIsForWatchingCommand request, CancellationToken cancellationToken) + { + var bot = _taskCache.Get(request.Name); + await bot.ToggleIsForWatchOnly(); + return bot.GetStatus(); + } + } +} diff --git a/src/Managing.Application/Managing.Application.csproj b/src/Managing.Application/Managing.Application.csproj new file mode 100644 index 0000000..6996dc5 --- /dev/null +++ b/src/Managing.Application/Managing.Application.csproj @@ -0,0 +1,34 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Managing.Application/MoneyManagements/MoneyManagementService.cs b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs new file mode 100644 index 0000000..0c2b63d --- /dev/null +++ b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs @@ -0,0 +1,79 @@ +using Managing.Domain.MoneyManagements; +using Managing.Application.Abstractions; +using Microsoft.Extensions.Logging; +using Managing.Application.Abstractions.Repositories; + +namespace Managing.Application.MoneyManagements; + +public class MoneyManagementService : IMoneyManagementService +{ + private readonly ISettingsRepository _settingsRepository; + private readonly ILogger _logger; + + public MoneyManagementService( + ILogger logger, + ISettingsRepository settingsRepository) + { + _logger = logger; + _settingsRepository = settingsRepository; + } + + public async Task CreateOrUpdateMoneyManagement(MoneyManagement request) + { + var moneyManagement = await _settingsRepository.GetMoneyManagement(request.Name); + + if (moneyManagement == null) + { + await _settingsRepository.InsertMoneyManagement(request); + } + else + { + moneyManagement.StopLoss = request.StopLoss; + moneyManagement.TakeProfit = request.TakeProfit; + moneyManagement.BalanceAtRisk = request.BalanceAtRisk; + moneyManagement.Leverage = request.Leverage; + moneyManagement.Timeframe = request.Timeframe; + _settingsRepository.UpdateMoneyManagement(moneyManagement); + } + + return moneyManagement; + } + + public IEnumerable GetMoneyMangements() + { + return _settingsRepository.GetMoneyManagements(); + } + + public async Task GetMoneyMangement(string name) + { + return await _settingsRepository.GetMoneyManagement(name); + } + + public bool DeleteMoneyManagement(string name) + { + try + { + _settingsRepository.DeleteMoneyManagement(name); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + + public bool DeleteMoneyManagements() + { + try + { + _settingsRepository.DeleteMoneyManagements(); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } +} diff --git a/src/Managing.Application/Scenarios/ScenarioService.cs b/src/Managing.Application/Scenarios/ScenarioService.cs new file mode 100644 index 0000000..a00ed09 --- /dev/null +++ b/src/Managing.Application/Scenarios/ScenarioService.cs @@ -0,0 +1,144 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using static Managing.Common.Enums; + +namespace Managing.Application.Scenarios +{ + public class ScenarioService : IScenarioService + { + private readonly ILogger _logger; + private readonly ITradingService _tradingService; + + public ScenarioService(ILogger logger, ITradingService tradingService) + { + _logger = logger; + _tradingService = tradingService; + } + + public Scenario CreateScenario(string name, List strategies) + { + var scenario = new Scenario(name); + + foreach (var strategy in strategies) + { + scenario.AddStrategy(_tradingService.GetStrategyByName(strategy)); + } + + try + { + _tradingService.InsertScenario(scenario); + } + catch (MongoCommandException ex) + { + _logger.LogError(ex.Message); + throw new Exception("Cannot create scenario"); + } + + return scenario; + } + + public Strategy CreateStrategy( + StrategyType type, + Timeframe timeframe, + 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) + { + var strategy = ScenarioHelpers.BuildStrategy( + type, + timeframe, + name, + period, + fastPeriods, + slowPeriods, + signalPeriods, + multiplier, + stochPeriods, + smoothPeriods, + cyclePeriods); + _tradingService.InsertStrategy(strategy); + return strategy; + } + + public IEnumerable GetScenarios() + { + return _tradingService.GetScenarios(); + } + + public Scenario GetScenario(string name) + { + return _tradingService.GetScenarioByName(name); + } + + public IEnumerable GetStrategies() + { + return _tradingService.GetStrategies(); + } + + public bool DeleteScenario(string name) + { + try + { + _tradingService.DeleteScenario(name); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + + public bool DeleteStrategy(string name) + { + try + { + _tradingService.DeleteStrategy(name); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + + public bool DeleteStrategies() + { + try + { + _tradingService.DeleteStrategies(); + } + catch (Exception) + { + return false; + } + + return true; + } + + public bool DeleteScenarios() + { + try + { + _tradingService.DeleteScenarios(); + } + catch (Exception) + { + return false; + } + + return true; + } + } +} diff --git a/src/Managing.Application/Shared/Behaviours/IUnhandledExceptionBehaviour.cs b/src/Managing.Application/Shared/Behaviours/IUnhandledExceptionBehaviour.cs new file mode 100644 index 0000000..a751874 --- /dev/null +++ b/src/Managing.Application/Shared/Behaviours/IUnhandledExceptionBehaviour.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Managing.Application.Shared.Behaviours +{ + public interface IUnhandledExceptionBehaviour + { + Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next); + } +} \ No newline at end of file diff --git a/src/Managing.Application/Shared/Behaviours/UnhandledExceptionBehaviour.cs b/src/Managing.Application/Shared/Behaviours/UnhandledExceptionBehaviour.cs new file mode 100644 index 0000000..dc07235 --- /dev/null +++ b/src/Managing.Application/Shared/Behaviours/UnhandledExceptionBehaviour.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Managing.Application.Shared.Behaviours +{ + public class UnhandledExceptionBehaviour : IUnhandledExceptionBehaviour + { + private readonly ILogger logger; + + public UnhandledExceptionBehaviour(ILogger logger) + { + this.logger = logger; + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + try + { + return await next(); + } + catch (Exception ex) + { + var requestName = typeof(TRequest).Name; + + logger.LogError(ex, $"Unhandled Exception for Request {requestName} {request}"); + + throw; + } + } + } +} diff --git a/src/Managing.Application/Shared/Behaviours/ValidationBehaviour.cs b/src/Managing.Application/Shared/Behaviours/ValidationBehaviour.cs new file mode 100644 index 0000000..7aa1e81 --- /dev/null +++ b/src/Managing.Application/Shared/Behaviours/ValidationBehaviour.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using MediatR; + +namespace Managing.Application.Shared.Behaviours +{ + public class ValidationBehaviour : IPipelineBehavior + where TRequest : IRequest + { + private readonly IEnumerable> validators; + + public ValidationBehaviour(IEnumerable> validators) + { + this.validators = validators; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (validators.Any()) + { + var context = new ValidationContext(request); + var validationResults = await Task.WhenAll(validators.Select(v => v.ValidateAsync(context, cancellationToken))); + var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList(); + if (failures.Count != 0) throw new ValidationException(failures); + } + + return await next(); + } + } +} diff --git a/src/Managing.Application/Shared/MessengerService.cs b/src/Managing.Application/Shared/MessengerService.cs new file mode 100644 index 0000000..92ab997 --- /dev/null +++ b/src/Managing.Application/Shared/MessengerService.cs @@ -0,0 +1,66 @@ +using Managing.Application.Abstractions.Services; +using Managing.Common; +using Managing.Domain.Statistics; +using Managing.Domain.Trades; + +namespace Managing.Application.Shared; + +public class MessengerService : IMessengerService +{ + private readonly IDiscordService _discordService; + + public MessengerService(IDiscordService discordService) + { + _discordService = discordService; + } + + public async Task SendClosedPosition(string address, Trade oldTrade) + { + await _discordService.SendClosedPosition(address, oldTrade); + } + + public async Task SendClosingPosition(Position position) + { + await _discordService.SendClosingPosition(position); + } + + public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null) + { + await _discordService.SendIncreasePosition(address, trade, copyAccountName, oldTrade); + } + + public async Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount) + { + await _discordService.SendDecreasePosition(address, newTrade, decreaseAmount); + } + + public async Task SendMessage(string message) + { + await _discordService.SendMessage(message); + } + + public async Task SendPosition(Position position) + { + await _discordService.SendPosition(position); + } + + public async Task SendSignal(string message, Enums.TradingExchanges exchange, Enums.Ticker ticker, Enums.TradeDirection direction, Enums.Timeframe timeframe) + { + await _discordService.SendSignal(message, exchange, ticker, direction, timeframe); + } + + public async Task SendTradeMessage(string message, bool isBadBehavior = false) + { + await _discordService.SendTradeMessage(message, isBadBehavior); + } + + public async Task SendBestTraders(List traders) + { + await _discordService.SendBestTraders(traders); + } + + public async Task SendBadTraders(List traders) + { + await _discordService.SendBadTraders(traders); + } +} diff --git a/src/Managing.Application/Shared/SettingsService.cs b/src/Managing.Application/Shared/SettingsService.cs new file mode 100644 index 0000000..76195c3 --- /dev/null +++ b/src/Managing.Application/Shared/SettingsService.cs @@ -0,0 +1,199 @@ +using Managing.Application.Abstractions; +using Managing.Domain.MoneyManagements; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Application.Shared; + +public class SettingsService : ISettingsService +{ + private readonly IMoneyManagementService _moneyManagementService; + private readonly IScenarioService _scenarioService; + private readonly IBacktester _backtester; + private readonly ILogger _logger; + + public SettingsService(IMoneyManagementService moneyManagementService, + IScenarioService scenarioService, + IBacktester backtester, + ILogger logger) + { + _moneyManagementService = moneyManagementService; + _scenarioService = scenarioService; + _backtester = backtester; + _logger = logger; + } + + public async Task ResetSettings() + { + if (!_backtester.DeleteBacktests()) + { + throw new Exception("Cannot delete all backtests"); + } + + if (!_scenarioService.DeleteScenarios()) + { + throw new Exception("Cannot delete scenarios"); + } + + if (!_scenarioService.DeleteStrategies()) + { + throw new Exception("Cannot delete all strategies"); + } + + if (!_moneyManagementService.DeleteMoneyManagements()) + { + throw new Exception("Cannot delete all money management settings"); + } + + if (!SetupSettings()) + { + throw new Exception("Cannot setup settings"); + } + + return await Task.FromResult(true); + } + + public bool SetupSettings() + { + try + { + SetupMoneyManagementsSeed(Timeframe.FiveMinutes); + SetupMoneyManagementsSeed(Timeframe.FifteenMinutes); + SetupMoneyManagementsSeed(Timeframe.OneHour); + SetupMoneyManagementsSeed(Timeframe.OneDay); + SetupScenariosSeed(Timeframe.FifteenMinutes); + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + 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} Money Management" + }; + + await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement); + } + + private void SetupScenariosSeed(Timeframe timeframe) + { + SetupMacd(timeframe); + SetupRsiDiv(timeframe); + SetupRsiDivConfirm(timeframe); + SetupSuperTrend(timeframe); + SetupChandelierExit(timeframe); + SetupStochRsiTrend(timeframe); + SetupStochSTCTrend(timeframe); + SetupEmaTrend(timeframe); + SetupEmaCross(timeframe); + } + + private void SetupStochSTCTrend(Timeframe timeframe) + { + var name = "STCTrend"; + var strategy = _scenarioService.CreateStrategy(StrategyType.Stc, + timeframe, + name, + fastPeriods: 23, + slowPeriods: 50, + cyclePeriods: 10); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupMacd(Timeframe timeframe) + { + var name = "MacdCross"; + var strategy = _scenarioService.CreateStrategy(StrategyType.MacdCross, + timeframe, + name, + fastPeriods: 12, + slowPeriods: 26, + signalPeriods: 9); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupRsiDiv(Timeframe timeframe) + { + var name = "RsiDiv6"; + var strategy = _scenarioService.CreateStrategy(StrategyType.RsiDivergence, + timeframe, + name, + period: 6); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupRsiDivConfirm(Timeframe timeframe) + { + var name = "RsiDivConfirm6"; + var strategy = _scenarioService.CreateStrategy(StrategyType.RsiDivergenceConfirm, + timeframe, + name, + period: 6); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupSuperTrend(Timeframe timeframe) + { + var name = "SuperTrend"; + var strategy = _scenarioService.CreateStrategy(StrategyType.SuperTrend, + timeframe, + name, + period: 10, + multiplier: 3); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupChandelierExit(Timeframe timeframe) + { + var name = "ChandelierExit"; + var strategy = _scenarioService.CreateStrategy(StrategyType.ChandelierExit, + timeframe, + name, + period: 22, + multiplier: 3); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + private void SetupStochRsiTrend(Timeframe timeframe) + { + var name = "StochRsiTrend"; + var strategy = _scenarioService.CreateStrategy(StrategyType.StochRsiTrend, + timeframe, + name, + period: 14, + stochPeriods: 14, + signalPeriods: 3, + smoothPeriods: 1); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupEmaTrend(Timeframe timeframe) + { + var name = "Ema200Trend"; + var strategy = _scenarioService.CreateStrategy(StrategyType.EmaTrend, + timeframe, + name, + period: 200); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } + + private void SetupEmaCross(Timeframe timeframe) + { + var name = "Ema200Cross"; + var strategy = _scenarioService.CreateStrategy(StrategyType.EmaCross, + timeframe, + name, + period: 200); + _scenarioService.CreateScenario(name, new List { strategy.Name }); + } +} diff --git a/src/Managing.Application/Shared/StreamService.cs b/src/Managing.Application/Shared/StreamService.cs new file mode 100644 index 0000000..c70f421 --- /dev/null +++ b/src/Managing.Application/Shared/StreamService.cs @@ -0,0 +1,30 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Hubs; +using Microsoft.AspNetCore.SignalR; + +namespace Managing.Application.Shared; + +public class StreamService : IStreamService +{ + private readonly IExchangeStream _exchangeStream; + private readonly IHubContext _hubContext; + + + public StreamService(IExchangeStream exchangeStream, IHubContext hubContext) + { + _exchangeStream = exchangeStream; + _hubContext = hubContext; + } + + public async Task SubscribeCandle() + { + await _exchangeStream.StartBinanceWorker(Common.Enums.Ticker.BTC, async (candle) => { + await _hubContext.Clients.All.SendAsync(candle.Ticker, candle); + }); + } + + public async Task UnSubscribeCandle() + { + await _exchangeStream.StopBinanceWorker(); + } +} diff --git a/src/Managing.Application/Shared/TickerService.cs b/src/Managing.Application/Shared/TickerService.cs new file mode 100644 index 0000000..54e061e --- /dev/null +++ b/src/Managing.Application/Shared/TickerService.cs @@ -0,0 +1,21 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using static Managing.Common.Enums; + +namespace Managing.Application.Shared; + +public class TickerService : ITickerService +{ + private readonly IEvmManager _evmManager; + + public TickerService(IEvmManager evmManager) + { + _evmManager = evmManager; + } + + public async Task> GetAvailableTicker() + { + var tickers = await _evmManager.GetAvailableTicker(); + return tickers; + } +} diff --git a/src/Managing.Application/Trading/ClosePositionCommandHandler.cs b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs new file mode 100644 index 0000000..477ab19 --- /dev/null +++ b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs @@ -0,0 +1,55 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.Shared.Helpers; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading; + +public class ClosePositionCommandHandler : ICommandHandler +{ + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ITradingService _tradingService; + + public ClosePositionCommandHandler( + IExchangeService exchangeService, + IAccountService accountService, + ITradingService tradingService) + { + _exchangeService = exchangeService; + _accountService = accountService; + _tradingService = tradingService; + } + + public async Task Handle(ClosePositionCommand request) + { + // 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 = _exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading).Result; + var closeRequestedOrders = isForPaperTrading ? true : _exchangeService.CancelOrder(account, request.Position.Ticker).Result; + + if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled)) + { + request.Position.Status = PositionStatus.Finished; + request.Position.ProfitAndLoss = TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice); + _tradingService.UpdatePosition(request.Position); + } + + return request.Position; + } +} diff --git a/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs new file mode 100644 index 0000000..ee70784 --- /dev/null +++ b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs @@ -0,0 +1,17 @@ +using Managing.Domain.Trades; +using MediatR; + +namespace Managing.Application.Trading.Commands +{ + public class ClosePositionCommand : IRequest + { + public ClosePositionCommand(Position position, decimal? executionPrice = null) + { + Position = position; + ExecutionPrice = executionPrice; + } + + public Position Position { get; } + public decimal? ExecutionPrice { get; set; } + } +} diff --git a/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs b/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs new file mode 100644 index 0000000..4b8b6bd --- /dev/null +++ b/src/Managing.Application/Trading/Commands/GetPositionsCommand.cs @@ -0,0 +1,16 @@ +using Managing.Common; +using Managing.Domain.Trades; +using MediatR; + +namespace Managing.Application.Trading.Commands +{ + public class GetPositionsCommand : IRequest> + { + public GetPositionsCommand(Enums.PositionInitiator initiator) + { + Initiator = initiator; + } + + public Enums.PositionInitiator Initiator { get; internal set; } + } +} diff --git a/src/Managing.Application/Trading/Commands/GetTradeCommand.cs b/src/Managing.Application/Trading/Commands/GetTradeCommand.cs new file mode 100644 index 0000000..a5dea6d --- /dev/null +++ b/src/Managing.Application/Trading/Commands/GetTradeCommand.cs @@ -0,0 +1,20 @@ +using Managing.Domain.Trades; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading.Commands +{ + public class GetTradeCommand : IRequest + { + public GetTradeCommand(string accountName, string exchangeOrderId, Ticker ticker) + { + AccountName = accountName; + ExchangeOrderId = exchangeOrderId; + Ticker = ticker; + } + + public string AccountName { get; } + public string ExchangeOrderId { get; } + public Ticker Ticker { get; } + } +} diff --git a/src/Managing.Application/Trading/Commands/GetTradesCommand.cs b/src/Managing.Application/Trading/Commands/GetTradesCommand.cs new file mode 100644 index 0000000..050a35c --- /dev/null +++ b/src/Managing.Application/Trading/Commands/GetTradesCommand.cs @@ -0,0 +1,18 @@ +using Managing.Domain.Trades; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading.Commands +{ + public class GetTradesCommand : IRequest> + { + public GetTradesCommand(Ticker ticker, string accountName) + { + Ticker = ticker; + AccountName = accountName; + } + + public string AccountName { get; } + public Ticker Ticker { get; } + } +} diff --git a/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs new file mode 100644 index 0000000..a325214 --- /dev/null +++ b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs @@ -0,0 +1,48 @@ +using Managing.Domain.MoneyManagements; +using Managing.Domain.Trades; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading.Commands +{ + public class OpenPositionRequest : IRequest + { + public OpenPositionRequest( + string accountName, + MoneyManagement moneyManagement, + TradeDirection direction, + Ticker ticker, + PositionInitiator initiator, + DateTime date, + bool isForPaperTrading = false, + decimal? price = null, + decimal? balance = 1000, + decimal? fee = null, + bool? ignoreSLTP = false) + { + AccountName = accountName; + MoneyManagement = moneyManagement; + Direction = direction; + Ticker = ticker; + IsForPaperTrading = isForPaperTrading; + Price = price; + Date = date; + Balance = balance; + Initiator = initiator; + Fee = fee; + IgnoreSLTP = ignoreSLTP; + } + + public string AccountName { get; } + public MoneyManagement MoneyManagement { get; } + public TradeDirection Direction { get; } + public Ticker Ticker { get; } + public bool IsForPaperTrading { get; } + public decimal? Price { get; } + public decimal? Fee { get; } + public bool? IgnoreSLTP { get; } + public decimal? Balance { get; } + public DateTime Date { get; set; } + public PositionInitiator Initiator { get; internal set; } + } +} diff --git a/src/Managing.Application/Trading/GetPositionsCommandHandler.cs b/src/Managing.Application/Trading/GetPositionsCommandHandler.cs new file mode 100644 index 0000000..e8c9521 --- /dev/null +++ b/src/Managing.Application/Trading/GetPositionsCommandHandler.cs @@ -0,0 +1,23 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using MediatR; + +namespace Managing.Application.Trading +{ + public class GetPositionsCommandHandler : IRequestHandler> + { + private readonly ITradingService _tradingService; + + public GetPositionsCommandHandler(ITradingService tradingService) + { + _tradingService = tradingService; + } + + public Task> Handle(GetPositionsCommand request, CancellationToken cancellationToken) + { + var positions = _tradingService.GetPositions(request.Initiator); + return Task.FromResult(positions.ToList()); + } + } +} diff --git a/src/Managing.Application/Trading/GetTradeCommandHandler.cs b/src/Managing.Application/Trading/GetTradeCommandHandler.cs new file mode 100644 index 0000000..9f81d62 --- /dev/null +++ b/src/Managing.Application/Trading/GetTradeCommandHandler.cs @@ -0,0 +1,23 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using MediatR; + +namespace Managing.Application.Trading; + +public class GetTradeCommandHandler : IRequestHandler +{ + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + public GetTradeCommandHandler(IExchangeService exchangeService, IAccountService accountService) + { + _exchangeService = exchangeService; + _accountService = accountService; + } + + public Task Handle(GetTradeCommand request, CancellationToken cancellationToken) + { + var account = _accountService.GetAccount(request.AccountName, true, false).Result; + return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker); + } +} diff --git a/src/Managing.Application/Trading/GetTradesCommandHandler.cs b/src/Managing.Application/Trading/GetTradesCommandHandler.cs new file mode 100644 index 0000000..f44a189 --- /dev/null +++ b/src/Managing.Application/Trading/GetTradesCommandHandler.cs @@ -0,0 +1,25 @@ +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using MediatR; + +namespace Managing.Application.Trading +{ + public class GetTradesCommandHandler : IRequestHandler> + { + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + + public GetTradesCommandHandler(IExchangeService exchangeService, IAccountService accountService) + { + _exchangeService = exchangeService; + _accountService = accountService; + } + + public Task> Handle(GetTradesCommand request, CancellationToken cancellationToken) + { + var account = _accountService.GetAccount(request.AccountName, true, false).Result; + return _exchangeService.GetTrades(account, request.Ticker); + } + } +} diff --git a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs new file mode 100644 index 0000000..bac248f --- /dev/null +++ b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs @@ -0,0 +1,128 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading.Commands; +using Managing.Domain.Shared.Helpers; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading +{ + public class OpenPositionCommandHandler : ICommandHandler + { + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ITradingService _tradingService; + + public OpenPositionCommandHandler( + IExchangeService exchangeService, + IAccountService accountService, + ITradingService tradingService) + { + _exchangeService = exchangeService; + _accountService = accountService; + _tradingService = tradingService; + } + + public Task Handle(OpenPositionRequest request) + { + var account = _accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false).Result; + if (!request.IsForPaperTrading && !_exchangeService.CancelOrder(account, request.Ticker).Result) + { + throw new Exception($"Not able to close all orders for {request.Ticker}"); + } + + var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator; + var position = new Position(request.AccountName, request.Direction, request.Ticker, request.MoneyManagement, initiator, request.Date); + var balance = request.IsForPaperTrading ? request.Balance.GetValueOrDefault() : _exchangeService.GetBalance(account, request.IsForPaperTrading).Result; + var balanceAtRisk = RiskHelpers.GetBalanceAtRisk(balance, request.MoneyManagement); + + if (balanceAtRisk < 13) + { + throw new Exception($"Try to risk {balanceAtRisk} $ but inferior to minimum to trade"); + } + + var price = request.IsForPaperTrading && request.Price.HasValue ? + request.Price.Value : + _exchangeService.GetPrice(account, request.Ticker, DateTime.Now); + var quantity = balanceAtRisk / price; + var fee = request.IsForPaperTrading ? request.Fee.GetValueOrDefault() : _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); + + var trade = _exchangeService.OpenTrade( + account, + request.Ticker, + request.Direction, + openPrice, + quantity, + request.MoneyManagement.Leverage, + TradeType.Limit, + isForPaperTrading: request.IsForPaperTrading, + currentDate: request.Date).Result; + + trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange); + return trade; + }); + + + if (IsOpenTradeHandled(position.Open.Status, account.Exchange) && !request.IgnoreSLTP.GetValueOrDefault()) + { + + 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); + + 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); + } + + position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange) ? position.Status : PositionStatus.Rejected; + _tradingService.InsertPosition(position); + + return Task.FromResult(position); + } + + private static TradeStatus GetExpectedStatus(OpenPositionRequest request) + { + if (request.IsForPaperTrading) + { + return TradeStatus.Filled; + } + + return TradeStatus.Requested; + } + + private static bool IsOpenTradeHandled(TradeStatus tradeStatus, TradingExchanges exchange) + { + return tradeStatus == TradeStatus.Filled + || (exchange == TradingExchanges.Evm && tradeStatus == TradeStatus.Requested); + } + } +} diff --git a/src/Managing.Application/Trading/TradingPolicies.cs b/src/Managing.Application/Trading/TradingPolicies.cs new file mode 100644 index 0000000..20399bc --- /dev/null +++ b/src/Managing.Application/Trading/TradingPolicies.cs @@ -0,0 +1,18 @@ +using Managing.Domain.Trades; +using Polly; +using Polly.Retry; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading; + +public static class TradingPolicies +{ + public static RetryPolicy OpenPosition(TradeStatus tradeStatus) + { + var policy = Policy + .HandleResult(res => res.Status != tradeStatus) + .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3)); + + return policy; + } +} diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs new file mode 100644 index 0000000..47871b5 --- /dev/null +++ b/src/Managing.Application/Trading/TradingService.cs @@ -0,0 +1,352 @@ +using DnsClient.Internal; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Domain.Accounts; +using Managing.Domain.Scenarios; +using Managing.Domain.Statistics; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Domain.Shared.Helpers; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using static Managing.Common.Enums; + +namespace Managing.Application.Trading; + +public class TradingService : ITradingService +{ + private readonly ITradingRepository _tradingRepository; + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; + private readonly ICacheService _cacheService; + private readonly IMessengerService _messengerService; + private readonly IStatisticRepository _statisticRepository; + private readonly ILogger _logger; + + public TradingService( + ITradingRepository tradingRepository, + IExchangeService exchangeService, + ILogger logger, + IAccountService accountService, + ICacheService cacheService, + IMessengerService messengerService, + IStatisticRepository statisticRepository) + { + _tradingRepository = tradingRepository; + _exchangeService = exchangeService; + _logger = logger; + _accountService = accountService; + _cacheService = cacheService; + _messengerService = messengerService; + _statisticRepository = statisticRepository; + } + + public void DeleteScenario(string name) + { + _tradingRepository.DeleteScenario(name); + } + + public void DeleteScenarios() + { + _tradingRepository.DeleteScenarios(); + } + + public void DeleteStrategies() + { + _tradingRepository.DeleteStrategies(); + } + + public void DeleteStrategy(string name) + { + _tradingRepository.DeleteStrategy(name); + } + + public Position GetPositionByIdentifier(string identifier) + { + return _tradingRepository.GetPositionByIdentifier(identifier); + } + + public IEnumerable GetPositions(PositionInitiator positionInitiator) + { + return _tradingRepository.GetPositions(positionInitiator); + } + + public IEnumerable GetPositionsByStatus(PositionStatus postionStatus) + { + return _tradingRepository.GetPositionsByStatus(postionStatus); + } + + + + public Scenario GetScenarioByName(string scenario) + { + return _tradingRepository.GetScenarioByName(scenario); + } + + public IEnumerable GetScenarios() + { + return _tradingRepository.GetScenarios(); + } + + public IEnumerable GetStrategies() + { + return _tradingRepository.GetStrategies(); + } + + public Strategy GetStrategyByName(string strategy) + { + return _tradingRepository.GetStrategyByName(strategy); + } + + public void InsertPosition(Position position) + { + _tradingRepository.InsertPosition(position); + } + + public void InsertScenario(Scenario scenario) + { + _tradingRepository.InsertScenario(scenario); + } + + public void InsertSignal(Signal signal) + { + _tradingRepository.InsertSignal(signal); + } + + public void InsertStrategy(Strategy strategy) + { + _tradingRepository.InsertStrategy(strategy); + } + + public async Task ManagePosition(Account account, Position position) + { + var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow); + var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker); + var orders = await _exchangeService.GetOpenOrders(account, position.Ticker); + + if (quantityInPosition > 0) + { + // Position still open + position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.Open.Quantity, lastPrice); + _logger.LogInformation($"Position is still open - PNL : {position.ProfitAndLoss.Realized} $"); + _logger.LogInformation($"Requested trades : {orders.Count}"); + } + else + { + // No quantity in position = SL/TP hit + if (orders.All(o => o.TradeType != TradeType.StopLoss)) + { + // SL hit + _logger.LogInformation($"Stop loss is filled on exchange."); + position.StopLoss.SetStatus(TradeStatus.Filled); + position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.StopLoss.Quantity, position.StopLoss.Price); + _ = _exchangeService.CancelOrder(account, position.Ticker); + } + else if (orders.All(o => o.TradeType != TradeType.TakeProfit)) + { + // TP Hit + if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit2 != null) + { + position.TakeProfit2.SetStatus(TradeStatus.Filled); + position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit2.Quantity, position.TakeProfit2.Price); + _logger.LogInformation($"TakeProfit 2 is filled on exchange."); + } + else + { + position.TakeProfit1.SetStatus(TradeStatus.Filled); + position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit1.Quantity, position.TakeProfit1.Price); + _logger.LogInformation($"TakeProfit 1 is filled on exchange."); + } + } + else + { + _logger.LogInformation($"Position closed manually or forced close by exchange because quantity in position is below 0."); + position.Status = PositionStatus.Finished; + + if (orders.Any()) await _exchangeService.CancelOrder(account, position.Ticker); + } + } + + return position; + } + + public void UpdateFee(TradingExchanges exchange) + { + var lastFee = _tradingRepository.GetFee(exchange); + var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); + if (lastFee != null) + { + if (DateTime.UtcNow.AddHours(-6) >= lastFee.LastUpdate) + { + lastFee.Cost = _exchangeService.GetFee(account); + lastFee.LastUpdate = DateTime.UtcNow; + _tradingRepository.UpdateFee(lastFee); + } + } + else + { + lastFee = new Fee + { + Cost = _exchangeService.GetFee(account), + Exchange = exchange, + LastUpdate = DateTime.UtcNow + }; + + _tradingRepository.InsertFee(lastFee); + } + } + + public decimal GetFee(Account account, bool isForPaperTrading = false) + { + if (isForPaperTrading && account.Exchange != TradingExchanges.Evm) + { + return 0.000665M; + } + + return _cacheService.GetOrSave($"Fee-{account.Exchange}", () => + { + return _tradingRepository.GetFee(TradingExchanges.Evm).Cost; + }, TimeSpan.FromHours(2)); + } + + public void UpdatePosition(Position position) + { + _tradingRepository.UpdatePosition(position); + } + + public IEnumerable GetPositions() + { + var positions = new List(); + positions.AddRange(GetPositionsByStatus(PositionStatus.New)); + positions.AddRange(GetPositionsByStatus(PositionStatus.Filled)); + positions.AddRange(GetPositionsByStatus(PositionStatus.PartiallyFilled)); + return positions; + } + + + public async Task WatchTrader() + { + var availableTickers = new List { Ticker.BTC, Ticker.ETH, Ticker.UNI, Ticker.LINK }; + var watchAccount = GetTradersWatch(); + var key = $"AccountsQuantityInPosition"; + var aqip = _cacheService.GetValue>(key); + + if (aqip == null) + { + aqip = GetAccountsQuantityInPosition(watchAccount); + } + else + { + foreach (var a in watchAccount.Where(w => !aqip.Any(a => a.Account.Address == w.Address))) + { + var newAccount = SetupFollowUp(a); + aqip.Add(newAccount); + } + + foreach (var a in aqip) + { + await ManageTrader(a, availableTickers); + } + + } + + _cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10)); + } + + public IEnumerable GetTradersWatch() + { + var watchAccount = _statisticRepository.GetBestTraders(); + var customWatchAccount = _accountService.GetAccounts(true, false).Where(a => a.Type == AccountType.Watch).ToList().MapToTraders(); + watchAccount.AddRange(customWatchAccount.Where(a => !watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase)))); + return watchAccount; + } + + private async Task ManageTrader(TraderFollowup a, List tickers) + { + var shortAddress = a.Account.Address.Substring(0, 6); + + foreach (var ticker in tickers) + { + try + { + var newTrade = await _exchangeService.GetTrade(a.Account.Address, "", ticker); + var oldTrade = a.Trades.SingleOrDefault(t => t.Ticker == ticker); + if (newTrade == null) + { + if (oldTrade != null) + { + _logger.LogInformation($"[{shortAddress}][{ticker}] Trader previously got a position open but the position was close by trader"); + await _messengerService.SendClosedPosition(a.Account.Address, oldTrade); + a.Trades.Remove(oldTrade); + } + } + else if ((newTrade != null && oldTrade == null) || (newTrade.Quantity > oldTrade.Quantity)) + { + _logger.LogInformation($"[{shortAddress}][{ticker}] Trader increase {newTrade.Direction} by {newTrade.Quantity - (oldTrade?.Quantity ?? 0)} with leverage {newTrade.Leverage} at {newTrade.Price} leverage."); + + var index = a.Trades.IndexOf(oldTrade); + if (index != -1) + { + a.Trades[index] = newTrade; + } + else + { + a.Trades.Add(newTrade); + } + + // Open position + await _messengerService.SendIncreasePosition(a.Account.Address, newTrade, "Test6", oldTrade); + // Save position to cache + } + else if (newTrade.Quantity < oldTrade.Quantity && newTrade.Quantity > 0) + { + var decreaseAmount = oldTrade.Quantity - newTrade.Quantity; + var index = a.Trades.IndexOf(oldTrade); + a.Trades[index] = newTrade; + _logger.LogInformation($"[{a.Account.Address.Substring(0, 6)}][{ticker}] Trader decrease position but didnt close it {decreaseAmount}"); + await _messengerService.SendDecreasePosition(a.Account.Address, newTrade, decreaseAmount); + } + else + { + _logger.LogInformation($"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}"); + } + } + catch (Exception ex) + { + _logger.LogError($"[{shortAddress}][{ticker}] Impossible to fetch trader"); + } + } + } + + private List GetAccountsQuantityInPosition(IEnumerable watchAccount) + { + var result = new List (); + foreach (var account in watchAccount) + { + var trader = SetupFollowUp(account); + result.Add(trader); + } + + return result; + } + + private static TraderFollowup SetupFollowUp(Trader account) + { + var trader = new TraderFollowup + { + Account = account, + Trades = new List(), + PositionIdentifiers = new List() + }; + + return trader; + } + + public class TraderFollowup + { + public Trader Account { get; set; } + public List Trades { get; set; } + public List PositionIdentifiers { get; set; } + } +} diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs new file mode 100644 index 0000000..c11d719 --- /dev/null +++ b/src/Managing.Application/Users/UserService.cs @@ -0,0 +1,88 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Domain.Accounts; +using Managing.Domain.Users; + +namespace Managing.Application.Users; + +public class UserService : IUserService +{ + private readonly IEvmManager _evmManager; + private readonly IUserRepository _userRepository; + private readonly IAccountService _accountService; + + public UserService( + IEvmManager evmManager, + IUserRepository userRepository, + IAccountService accountService) + { + _evmManager = evmManager; + _userRepository = userRepository; + _accountService = accountService; + } + + public async Task Authenticate(string name, string address, string message, string signature) + { + var recoveredAddress = _evmManager.VerifySignature(signature, message); + + if (recoveredAddress == null || !recoveredAddress.Equals(address)) + throw new Exception("Address not corresponding"); + + // Check if account exist + var account = await _accountService.GetAccountByKey(recoveredAddress, true, false); + var user = await _userRepository.GetUserByNameAsync(name); + + if (account != null && user != null) + { + // User and account found + user.Accounts = _accountService.GetAccountsByUser(user).ToList(); + + if (!user.Name.Equals(name)) + throw new Exception("Name not corresponding"); + + return user; + } + else + { + // No account and no + account = new Account + { + Name = $"Auth-{new Random().Next(1, 99)}", + Key = recoveredAddress, + Secret = "", + Exchange = Common.Enums.TradingExchanges.Evm, + Type = Common.Enums.AccountType.Auth, + }; + + if (user != null) + { + _ = 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 }, + }; + + _ = await _accountService.CreateAccount(user, account); + await _userRepository.InsertUserAsync(user); + } + } + + return user; + } + + public async Task GetUserByAddressAsync(string address) + { + var account = await _accountService.GetAccountByKey(address, true, false); + var user = await _userRepository.GetUserByNameAsync(account.User.Name); + user.Accounts = _accountService.GetAccountsByUser(user).ToList(); + return user; + } +} diff --git a/src/Managing.Application/Workflows/FlowFactory.cs b/src/Managing.Application/Workflows/FlowFactory.cs new file mode 100644 index 0000000..7d04213 --- /dev/null +++ b/src/Managing.Application/Workflows/FlowFactory.cs @@ -0,0 +1,51 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Workflows.Flows.Feeds; +using Managing.Application.Workflows.Flows.Trading; +using Managing.Domain.Workflows; +using Managing.Domain.Workflows.Synthetics; +using static Managing.Common.Enums; + +namespace Managing.Application.Workflows; + +public class FlowFactory : IFlowFactory +{ + private readonly IExchangeService _exchangeService; + private readonly ICacheService _cacheService; + private readonly ITradingService _tradingService; + private readonly IAccountService _accountService; + + public FlowFactory(IExchangeService exchangeService, ICacheService cacheService, ITradingService tradingService, IAccountService accountService) + { + _exchangeService = exchangeService; + _cacheService = cacheService; + _tradingService = tradingService; + _accountService = accountService; + } + + public IFlow BuildFlow(SyntheticFlow request) + { + IFlow flow = request.Type switch + { + FlowType.FeedTicker => new FeedTicker(_exchangeService), + FlowType.RsiDivergence => new RsiDiv(), + FlowType.OpenPosition => new OpenPosition(_exchangeService, _cacheService, _accountService, _tradingService), + _ => throw new NotImplementedException(), + }; + + flow.Children = new List(); + flow.Parameters = new List(); + + foreach (var parameter in request.Parameters) + { + if (!flow.Parameters.Any(p => p.Name == parameter.Name)) { + flow.Parameters.Add(new FlowParameter + { + Name = parameter.Name, + Value = parameter.Value + }); + } + } + return flow; + } +} diff --git a/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs b/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs new file mode 100644 index 0000000..f0087bb --- /dev/null +++ b/src/Managing.Application/Workflows/Flows/Feeds/FeedTicker.cs @@ -0,0 +1,64 @@ +using Managing.Application.Abstractions.Services; +using Managing.Domain.Workflows; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Application.Workflows.Flows.Feeds; + +public class FeedTicker : FlowBase +{ + public override List Children { get; set; } + public override List Parameters { get; set; } + public override Guid ParentId { get; } + public override Guid Id { get; } + public override string Output { get; set; } + public override string Name => "Feed Ticker"; + public override FlowType Type => FlowType.FeedTicker; + public override string Description => "This flow will take a feed and output the candles"; + public FeedTickerParameters FeedTickerParameters { get; set; } + public override List AcceptedInputs => new(); + public override List OutputTypes => new() { FlowOutput.Candles }; + private readonly IExchangeService _exchangeService; + + public FeedTicker(IExchangeService exchangeService) + { + _exchangeService = exchangeService; + } + + public async override Task Execute(string input) + { + MapParameters(); + var candles = await _exchangeService.GetCandlesInflux(FeedTickerParameters.Exchange, FeedTickerParameters.Ticker, DateTime.Now.AddDays(-11), FeedTickerParameters.Timeframe); + + Output = JsonConvert.SerializeObject(candles.ToHashSet()); + + if(Children != null && Children.Count > 0) + { + foreach (var child in Children) + { + await child.Execute(Output); + } + } + } + + public override void MapParameters() + { + FeedTickerParameters = new FeedTickerParameters(); + foreach (var param in Parameters) + { + if (param.Name == nameof(FeedTickerParameters.Ticker)) + FeedTickerParameters.Ticker = (Ticker)Enum.Parse(typeof(Ticker), param.Value); + if (param.Name == nameof(FeedTickerParameters.Exchange)) + FeedTickerParameters.Exchange = (TradingExchanges)Enum.Parse(typeof(TradingExchanges), param.Value); + if (param.Name == nameof(FeedTickerParameters.Timeframe)) + FeedTickerParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value); + } + } +} + +public class FeedTickerParameters +{ + public TradingExchanges Exchange { get; set; } + public Ticker Ticker { get; set; } + public Timeframe Timeframe { get; set; } +} diff --git a/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs b/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs new file mode 100644 index 0000000..3948267 --- /dev/null +++ b/src/Managing.Application/Workflows/Flows/Strategies/RsiDiv.cs @@ -0,0 +1,65 @@ +using Managing.Domain.Candles; +using Managing.Domain.Strategies; +using Managing.Domain.Workflows; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Application.Workflows.Flows.Feeds; + +public class RsiDiv : FlowBase +{ + public override List Children { get; set; } + public override List Parameters { get; set; } + public override Guid ParentId { get; } + public override Guid Id { get; } + public override string Output { get; set; } + public override string Name => "Rsi Divergence"; + public override string Description => "This flow will take a feed of candle an run the RSI Divergence"; + public override FlowType Type => FlowType.RsiDivergence; + public override List AcceptedInputs => new() { FlowOutput.Candles }; + public override List OutputTypes => new() { FlowOutput.Signal }; + public RsiDivParameters RsiDivParameters { get; set; } + + public async override Task Execute(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentException($"'{nameof(input)}' cannot be null or empty.", nameof(input)); + } + + MapParameters(); + var candles = JsonConvert.DeserializeObject>(input); + + var strategy = new RSIDivergenceStrategy(Name, RsiDivParameters.Timeframe, RsiDivParameters.Period); + strategy.UpdateCandles(candles); + strategy.Run(); + + Output = JsonConvert.SerializeObject(strategy.Signals); + + if(Children != null && Children.Count > 0) + { + foreach (var child in Children) + { + await child.Execute(Output); + } + } + } + + public override void MapParameters() + { + RsiDivParameters = new RsiDivParameters(); + foreach (var param in Parameters) + { + if (param.Name == nameof(RsiDivParameters.Period)) + RsiDivParameters.Period = int.Parse(param.Value); + if (param.Name == nameof(RsiDivParameters.Timeframe)) + RsiDivParameters.Timeframe = (Timeframe)Enum.Parse(typeof(Timeframe), param.Value); + } + } +} + +public class RsiDivParameters +{ + public int Period { get; set; } + public Timeframe Timeframe { get; set; } +} diff --git a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs new file mode 100644 index 0000000..67eda2b --- /dev/null +++ b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs @@ -0,0 +1,230 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Accounts; +using Managing.Application.Shared; +using Managing.Application.Trading.Commands; +using Managing.Application.Trading; +using Managing.Domain.Accounts; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Domain.Workflows; +using Newtonsoft.Json; +using static Managing.Common.Enums; +using Managing.Domain.Candles; +using Managing.Application.Abstractions.Repositories; + +namespace Managing.Application.Workflows.Flows.Trading; + +public class OpenPosition : FlowBase +{ + public override List Children { get; set; } + public override List Parameters { get; set; } + public override Guid ParentId { get; } + public override Guid Id { get; } + public override string Output { get; set; } + public override string Name => "Open Position"; + public override FlowType Type => FlowType.OpenPosition; + public override string Description => "This flow will open a position for a given signal"; + public OpenPositionParameters OpenPositionParameters { get; set; } + public override List AcceptedInputs => new() { FlowOutput.Signal, FlowOutput.MoneyManagement }; + public override List OutputTypes => new() { FlowOutput.Position }; + private readonly IExchangeService _exchangeService; + private readonly ICacheService _cacheService; + private readonly IAccountService _accountService; + private readonly ITradingService _tradingService; + private readonly ISettingsRepository _settingsRepository; + private readonly IMessengerService _messengerService; + + private readonly string POSITIONS_KEY = "positions"; + private readonly string ACCOUNT_KEY = "account"; + private readonly string CANDLES_KEY = "candles"; + private readonly string SIGNALS_KEY = "signals"; + private readonly string FEE_KEY = "fee"; + private readonly string WALLET_BALANCES = "wallet-balance"; + private decimal Fee { get; set; } + + + public OpenPosition( + IExchangeService exchangeService, + ICacheService cacheService, + IAccountService accountService, + ITradingService tradingService) + { + _exchangeService = exchangeService; + _cacheService = cacheService; + _accountService = accountService; + _tradingService = tradingService; + } + + public async override Task Execute(string input) + { + MapParameters(); + var signal = JsonConvert.DeserializeObject(input); + var Candles = JsonConvert.DeserializeObject>(_cacheService.GetValue(CANDLES_KEY)); + var Account = JsonConvert.DeserializeObject(_cacheService.GetValue(ACCOUNT_KEY)); + var Positions = JsonConvert.DeserializeObject>(_cacheService.GetValue(POSITIONS_KEY)); + var Signals = JsonConvert.DeserializeObject>(_cacheService.GetValue(POSITIONS_KEY)); + + Fee = _cacheService.GetOrSave(FEE_KEY, () => + { + return _tradingService.GetFee(Account, OpenPositionParameters.IsForBacktest); + }, TimeSpan.FromDays(1)); + + await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account); + + _cacheService.SaveValue(POSITIONS_KEY, JsonConvert.SerializeObject(Positions)); + _cacheService.SaveValue(SIGNALS_KEY, JsonConvert.SerializeObject(Signals)); + + if (Children != null && Children.Count > 0) + { + foreach (var child in Children) + { + await child.Execute(Output); + } + } + } + + private async Task ExecuteOpenPosition(Signal signal, List positions, HashSet signals, HashSet candles, Account account) + { + // Check if a position is already open + var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled + && p.SignalIdentifier != signal.Identifier); + + var lastPrice = OpenPositionParameters.IsForBacktest ? candles.Last().Close : _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow); + + // If position open + if (openedPosition != null) + { + var previousSignal = signals.First(s => s.Identifier == openedPosition.SignalIdentifier); + + // Check if signal is the opposite side => flip the position + if (openedPosition.OriginDirection == signal.Direction) + { + // An operation is already open for the same direction + //await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction"); + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + } + else + { + // An operation is already open for the opposite direction + // ==> Flip the position + if (OpenPositionParameters.FlipPosition) + { + //await LogInformation("Try to flip the position because of an opposite direction signal"); + //await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); + positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status = PositionStatus.Flipped; + await ExecuteOpenPosition(signal, positions, signals, candles, account); + + //await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$"); + } + else + { + //await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped."); + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + } + } + } + else + { + if (!CanOpenPosition(signal, positions, signals, candles)) + { + //await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + return; + } + //await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}"); + + try + { + var moneyManagement = await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName); + var WalletBalances = JsonConvert.DeserializeObject>(_cacheService.GetValue(WALLET_BALANCES)); + + var command = new OpenPositionRequest( + OpenPositionParameters.AccountName, + moneyManagement, + signal.Direction, + signal.Ticker, + PositionInitiator.Bot, + signal.Date, + OpenPositionParameters.IsForBacktest, + lastPrice, + balance: WalletBalances.LastOrDefault().Value, + fee: Fee); + + var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService) + .Handle(command); + + if (position != null) + { + if (position.Open.Status != TradeStatus.Cancelled) + { + position.SignalIdentifier = signal.Identifier; + positions.Add(position); + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.PositionOpen; + + if (!OpenPositionParameters.IsForBacktest) + { + await _messengerService.SendPosition(position); + } + Output = JsonConvert.SerializeObject(position); + //Logger.LogInformation($"Position requested"); + } + else + { + positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = PositionStatus.Rejected; + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + } + } + } + catch (Exception ex) + { + signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + //await LogWarning($"Cannot open trade : {ex.Message}"); + } + } + + } + + private bool CanOpenPosition(Signal signal, List positions, HashSet signals, HashSet candles) + { + if (positions.Count == 0) + return true; + + var lastPosition = positions.LastOrDefault(p => p.IsFinished() + && p.SignalIdentifier != signal.Identifier + && p.ProfitAndLoss.Realized < 0 + && p.OriginDirection == signal.Direction); + + if (lastPosition == null) + return true; + + var tenCandleAgo = candles.TakeLast(10).First(); + var positionSignal = signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier); + + return positionSignal.Date < tenCandleAgo.Date; + } + + + public override void MapParameters() + { + OpenPositionParameters = new OpenPositionParameters(); + foreach (var param in Parameters) + { + if (param.Name == nameof(OpenPositionParameters.AccountName)) + OpenPositionParameters.AccountName = param.Value; + if (param.Name == nameof(OpenPositionParameters.MoneyManagementName)) + OpenPositionParameters.MoneyManagementName = param.Value; + if (param.Name == nameof(OpenPositionParameters.FlipPosition)) + OpenPositionParameters.FlipPosition = bool.Parse(param.Value); + } + } +} + +public class OpenPositionParameters +{ + public string MoneyManagementName { get; set; } + public string AccountName { get; set; } + public bool IsForBacktest { get; set; } + public bool FlipPosition { get; set; } +} diff --git a/src/Managing.Application/Workflows/WorkflowService.cs b/src/Managing.Application/Workflows/WorkflowService.cs new file mode 100644 index 0000000..77f3e95 --- /dev/null +++ b/src/Managing.Application/Workflows/WorkflowService.cs @@ -0,0 +1,126 @@ +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Application.Workflows.Flows.Feeds; +using Managing.Application.Workflows.Flows.Trading; +using Managing.Domain.Workflows; +using Managing.Domain.Workflows.Synthetics; + +namespace Managing.Application.Workflows; + +public class WorkflowService : IWorkflowService +{ + private readonly IWorkflowRepository _workflowRepository; + private readonly IExchangeService _exchangeService; + private readonly IFlowFactory _flowFactory; + private readonly ICacheService _cacheService; + private readonly ITradingService _tradingService; + private readonly IAccountService _accountService; + + public WorkflowService( + IWorkflowRepository workflowRepository, + IExchangeService exchangeService, + IFlowFactory flowFactory, + ICacheService cacheService, + ITradingService tradingService, + IAccountService accountService) + { + _workflowRepository = workflowRepository; + _exchangeService = exchangeService; + _flowFactory = flowFactory; + _cacheService = cacheService; + _tradingService = tradingService; + _accountService = accountService; + } + + public async Task InsertOrUpdateWorkflow(SyntheticWorkflow workflowRequest) + { + var previousWorkflow = await _workflowRepository.GetWorkflow(workflowRequest.Name); + + try + { + if (previousWorkflow != null) + { + await _workflowRepository.UpdateWorkflow(workflowRequest); + } + else + { + await _workflowRepository.InsertWorkflow(workflowRequest); + } + } + catch (Exception ex) + { + + throw; + } + + return Map(workflowRequest); + } + + private Workflow Map(SyntheticWorkflow workflowRequest) + { + var workflow = new Workflow + { + Name = workflowRequest.Name, + Usage = workflowRequest.Usage, + Description = workflowRequest.Description, + Flows = new List() + }; + + // Add first flow that dont have any parent + var firstFlow = workflowRequest.Flows.SingleOrDefault(f => string.IsNullOrEmpty(f.ParentId)); + + if (firstFlow != null) + { + var flow = Build(workflowRequest.Flows, firstFlow); + workflow.Flows.Add(flow); + } + else + { + // TODO : Throw exception + throw new Exception("No first flow found"); + } + + return workflow; + } + + private IFlow Build(List flows, SyntheticFlow firstFlow) + { + var flow = _flowFactory.BuildFlow(firstFlow); + + foreach (var flowRequest in flows.Where(f => f.ParentId == firstFlow.Id)) + { + flow.Children.Add(Build(flows, flowRequest)); + } + + return flow; + } + + public IEnumerable GetWorkflows() + { + return _workflowRepository.GetWorkflows(); + } + + public bool DeleteWorkflow(string name) + { + return _workflowRepository.DeleteWorkflow(name); + } + + public async Task GetWorkflow(string name) + { + return Map(await _workflowRepository.GetWorkflow(name)); + } + + public Task> GetAvailableFlows() + { + var availableFlows = new List + { + new FeedTicker(_exchangeService), + new RsiDiv(), + new OpenPosition(_exchangeService, _cacheService, _accountService, _tradingService) + }; + + return Task.FromResult(availableFlows.AsEnumerable()); + } + +} diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs new file mode 100644 index 0000000..ae46644 --- /dev/null +++ b/src/Managing.Bootstrap/ApiBootstrap.cs @@ -0,0 +1,157 @@ +using FluentValidation; +using Managing.Application.Shared.Behaviours; +using MediatR; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Managing.Infrastructure.Exchanges; +using Managing.Infrastructure.Storage; +using Managing.Infrastructure.Databases.MongoDb; +using Microsoft.Extensions.Options; +using Managing.Application.Trading; +using Managing.Infrastructure.Messengers.Discord; +using Managing.Infrastructure.Evm; +using Discord.WebSocket; +using Microsoft.AspNetCore.Hosting; +using Discord.Commands; +using Managing.Application.Backtesting; +using Managing.Application.Scenarios; +using Managing.Application.MoneyManagements; +using Managing.Application.Accounts; +using Managing.Application.Abstractions; +using Managing.Application.Shared; +using Managing.Infrastructure.Databases; +using Managing.Infrastructure.Databases.Abstractions; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Managing.Infrastructure.Databases.InfluxDb; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Exchanges.Abstractions; +using Managing.Infrastructure.Exchanges.Exchanges; +using Managing.Application.Users; +using Managing.Infrastructure.Evm.Subgraphs; +using Managing.Application.ManageBot.Commands; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Binance.Net.Clients; +using Binance.Net.Interfaces.Clients; +using Managing.Infrastructure.Evm.Services; +using Managing.Application.Workflows; +using Managing.Application.Bots.Base; + +namespace Managing.Bootstrap; + +public static class ApiBootstrap +{ + + private static readonly Assembly ApplicationAssembly = typeof(StartBotCommand).GetTypeInfo().Assembly; + + public static IServiceCollection RegisterApiDependencies(this IServiceCollection services, IConfiguration configuration) + { + return services + .AddApplication() + .AddInfrastructure(configuration) + .AddFluentValidation() + .AddMediatR(); + } + + private static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient, OpenPositionCommandHandler>(); + services.AddTransient, ClosePositionCommandHandler>(); + + return services; + } + + private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + // Database + services.AddSingleton(sp => + sp.GetRequiredService>().Value); + + services.AddTransient(typeof(IMongoRepository<>), typeof(MongoRepository<>)); + + services.AddSingleton(sp => + sp.GetRequiredService>().Value); + + // Evm + services.AddGbcFeed(); + services.AddUniswapV2(); + services.AddChainlink(); + services.AddChainlinkGmx(); + services.AddSingleton(); + + // Repositories + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // Cache + services.AddDistributedMemoryCache(); + services.AddTransient(); + services.AddTransient(); + + // Processors + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // Services + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + + // Stream + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + private static IServiceCollection AddMediatR(this IServiceCollection services) + { + return services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies()) + .AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)) + .AddTransient(typeof(IUnhandledExceptionBehaviour<,>), typeof(UnhandledExceptionBehaviour<,>)); + } + + private static IServiceCollection AddFluentValidation(this IServiceCollection services) + { + return services.AddValidatorsFromAssembly(ApplicationAssembly); + } + + public static IWebHostBuilder SetupDiscordBot(this IWebHostBuilder builder) + { + return builder.ConfigureServices(services => + { + services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddHostedService(); + }); + } +} diff --git a/src/Managing.Bootstrap/Managing.Bootstrap.csproj b/src/Managing.Bootstrap/Managing.Bootstrap.csproj new file mode 100644 index 0000000..b63fd55 --- /dev/null +++ b/src/Managing.Bootstrap/Managing.Bootstrap.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Managing.Bootstrap/WorkersBootstrap.cs b/src/Managing.Bootstrap/WorkersBootstrap.cs new file mode 100644 index 0000000..b6676e1 --- /dev/null +++ b/src/Managing.Bootstrap/WorkersBootstrap.cs @@ -0,0 +1,117 @@ +using Binance.Net.Clients; +using Binance.Net.Interfaces.Clients; +using Kraken.Net.Clients; +using Kraken.Net.Interfaces.Clients; +using Managing.Application.Abstractions; +using Managing.Application.Accounts; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; +using Managing.Infrastructure.Exchanges; +using Managing.Infrastructure.Databases; +using Managing.Infrastructure.Databases.Abstractions; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Managing.Infrastructure.Databases.MongoDb; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Managing.Infrastructure.Databases.InfluxDb; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Evm.Subgraphs; +using Managing.Infrastructure.Evm; +using Managing.Infrastructure.Storage; +using Managing.Infrastructure.Exchanges.Abstractions; +using Managing.Infrastructure.Exchanges.Exchanges; +using Managing.Application.Trading; +using Managing.Application.Backtesting; +using Managing.Application.MoneyManagements; +using Managing.Application.Scenarios; +using Managing.Application.Shared; +using Managing.Infrastructure.Messengers.Discord; +using Managing.Application.Trading.Commands; +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm.Services; +using Managing.Application.Bots.Base; + +namespace Managing.Bootstrap; + +public static class WorkersBootstrap +{ + public static IServiceCollection RegisterWorkersDependencies(this IServiceCollection services, IConfiguration configuration) + { + return services + .AddApplication() + .AddInfrastructure(configuration); + } + + private static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient, OpenPositionCommandHandler>(); + services.AddTransient, ClosePositionCommandHandler>(); + + return services; + } + + private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + // Database + services.AddSingleton(sp => + sp.GetRequiredService>().Value); + services.AddTransient(typeof(IMongoRepository<>), typeof(MongoRepository<>)); + + services.AddSingleton(sp => + sp.GetRequiredService>().Value); + + services.AddTransient(); + + // Evm + services.AddUniswapV2(); + services.AddGbcFeed(); + services.AddChainlink(); + services.AddChainlinkGmx(); + services.AddSingleton(); + + // Repositories + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // Cache + services.AddDistributedMemoryCache(); + services.AddTransient(); + services.AddTransient(); + + // Processors + services.AddTransient(); + + // Web Clients + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + + // Messengers + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} diff --git a/src/Managing.Common/Constants.cs b/src/Managing.Common/Constants.cs new file mode 100644 index 0000000..a91688a --- /dev/null +++ b/src/Managing.Common/Constants.cs @@ -0,0 +1,39 @@ +namespace Managing.Common +{ + public class Constants + { + public class DiscordSlashCommand + { + public const string Leaderboard = "leaderboard"; + public const string Noobiesboard = "noobiesboard"; + public const string LeaderboardPosition = "leaderboardposition"; + } + + public class DiscordButtonAction + { + public const string OpenPosition = "openposition"; + public const string ClosePosition = "closeposition"; + public const string CopyPosition = "ctp"; + } + + public class Databases + { + public const string InfluxDb = "InfluxDb"; + public const string MongoDb = "ManagingDatabase"; + } + + public class Chains + { + public const string Ethereum = "Ethereum"; + public const string Arbitrum = "Arbitrum"; + public const string ArbitrumGoerli = "ArbitrumGoerli"; + public const string Goerli = "Goerli"; + } + + public class Stablecoins + { + public const string Usd = "USD"; + public const string Usdt = "USDT"; + } + } +} diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs new file mode 100644 index 0000000..6bf4d83 --- /dev/null +++ b/src/Managing.Common/Enums.cs @@ -0,0 +1,343 @@ +namespace Managing.Common; + +public static class Enums +{ + public enum TradingExchanges + { + Binance, + Kraken, + Ftx, + Evm + } + + public enum GmxOrderType + { + Increase, + Decrease, + Swap + } + + public enum SubgraphProvider + { + UniswapV2, + ChainlinkPrice, + ChainlinkGmx, + Gbc + } + + public enum WorkerAction + { + Start, + Stop + } + + public enum AccountType + { + Cex, + Trader, + Watch, + Auth + } + + public enum BotType + { + SimpleBot, + ScalpingBot, + FlippingBot + } + + public enum StrategyType + { + RsiDivergence, + RsiDivergenceConfirm, + MacdCross, + EmaCross, + ThreeWhiteSoldiers, + SuperTrend, + ChandelierExit, + EmaTrend, + Composite, + StochRsiTrend, + Stc, + StDev + } + + public enum SignalType + { + Signal, + Trend, + Context + } + + public enum BotStatus + { + Down, + Starting, + Up + } + + public enum SignalStatus + { + WaitingForPosition, + PositionOpen, + Expired + } + + public enum Confidence + { + Low, + Medium, + High, + None + } + + public enum TradeDirection + { + None, + Short, + Long + } + + public enum RiskLevel + { + Low, + Medium, + High, + Adaptive + } + + public enum TradeType + { + // + // Summary: + // Limit order + Limit = 0, + // + // Summary: + // Symbol order + Market = 1, + // + // Summary: + // Stop market order + StopMarket = 2, + // + // Summary: + // Stop limit order + StopLimit = 3, + // + // Summary: + // Stop loss order + StopLoss = 4, + // + // Summary: + // Take profit order + TakeProfit = 5, + // + // Summary: + // Stop loss profit order + StopLossProfit = 6, + // + // Summary: + // Stop loss profit limit order + StopLossProfitLimit = 7, + // + // Summary: + // Stop loss limit order + StopLossLimit = 8, + // + // Summary: + // Take profit limit order + TakeProfitLimit = 9, + // + // Summary: + // Trailing stop order + TrailingStop = 10, + // + // Summary: + // Trailing stop limit order + TrailingStopLimit = 11, + // + // Summary: + // Stop loss and limit order + StopLossAndLimit = 12, + // + // Summary: + // Settle position + SettlePosition = 13 + } + + public enum TradeStatus + { + PendingOpen = 0, + Requested = 1, + Cancelled = 2, + Filled = 3, + } + + public enum PositionStatus + { + New = 0, + Canceled = 1, + Rejected = 2, + Updating = 3, + PartiallyFilled = 4, + Filled = 5, + Flipped = 6, + Finished = 7 + } + + public enum PositionInitiator + { + PaperTrading, + Bot, + User, + CopyTrading + } + + public enum Timeframe + { + /// + /// 5m + /// + FiveMinutes, + /// + /// 15m + /// + FifteenMinutes, + /// + /// 30m + /// + ThirtyMinutes, + /// + /// 1h + /// + OneHour, + /// + /// 4h + /// + FourHour, + /// + /// 1d + /// + OneDay, + } + + public enum Ticker + { + ADA, + APE, + ALICE, + ALGO, + ATOM, + AVAX, + AXS, + BAT, + BNB, + BTC, + BAL, + C98, + CHR, + CHZ, + COMP, + CRO, + CRV, + CVC, + DEFI, + DOGE, + DOT, + DYDX, + ENS, + ETC, + ETH, + FIL, + FLM, + FTM, + GALA, + GMT, + GMX, + GRT, + HNT, + IMX, + JASMY, + KAVA, + KSM, + LDO, + LINK, + LOOKS, + LRC, + LTC, + MANA, + MATIC, + MKR, + NEAR, + NEO, + OMG, + ONE, + ONT, + QTUM, + REEF, + REN, + ROSE, + RSR, + RUNE, + SAND, + SOL, + SRM, + STMX, + SUSHI, + SXP, + THETA, + UNI, + USDC, + USDT, + VET, + WAVES, + XMR, + XRP, + XTZ, + YFI, + ZEC, + ZIL + } + + public enum WorkerType + { + PriceOneMinute, + PriceFiveMinutes, + PriceFifteenMinutes, + PriceThirtyMinutes, + PriceOneHour, + PriceFourHour, + PriceOneDay, + TopVolumeTicker, + PositionManager, + Spotlight, + Fee, + PositionFetcher, + TraderWatcher, + LeaderboardWorker, + Noobiesboard + } + + public enum WorkflowUsage + { + Trading, + Task + } + + public enum FlowType + { + RsiDivergence, + FeedTicker, + OpenPosition + } + + public enum FlowOutput + { + Signal, + Candles, + Position, + MoneyManagement + } + +} diff --git a/src/Managing.Common/Managing.Common.csproj b/src/Managing.Common/Managing.Common.csproj new file mode 100644 index 0000000..b8ca5ae --- /dev/null +++ b/src/Managing.Common/Managing.Common.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + diff --git a/src/Managing.Core/AsyncLazy.cs b/src/Managing.Core/AsyncLazy.cs new file mode 100644 index 0000000..dece47c --- /dev/null +++ b/src/Managing.Core/AsyncLazy.cs @@ -0,0 +1,22 @@ +using System.Runtime.CompilerServices; + +namespace Managing.Core +{ + /// + /// http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/asynclazy-lt-t-gt.aspx + /// + /// + public class AsyncLazy : Lazy> + { + public AsyncLazy(Func valueFactory) : + base(() => Task.Factory.StartNew(valueFactory)) + { } + + public AsyncLazy(Func> taskFactory) : + base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) + { } + + public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); } + } + +} diff --git a/src/Managing.Core/DateHelpers.cs b/src/Managing.Core/DateHelpers.cs new file mode 100644 index 0000000..2d5f14f --- /dev/null +++ b/src/Managing.Core/DateHelpers.cs @@ -0,0 +1,10 @@ +namespace Managing.Core; + +public class DateHelpers +{ + public static DateTime GetFromUnixTimestamp(int unixTimestamp) + { + var dat_Time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local); + return dat_Time.AddSeconds(unixTimestamp); + } +} diff --git a/src/Managing.Core/Managing.Core.csproj b/src/Managing.Core/Managing.Core.csproj new file mode 100644 index 0000000..d0c5e78 --- /dev/null +++ b/src/Managing.Core/Managing.Core.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + + diff --git a/src/Managing.Core/MathHelpers.cs b/src/Managing.Core/MathHelpers.cs new file mode 100644 index 0000000..d6f4e29 --- /dev/null +++ b/src/Managing.Core/MathHelpers.cs @@ -0,0 +1,25 @@ +namespace Managing.Core +{ + public static class MathHelpers + { + public static int GetDecimalPlaces(decimal n) + { + n = Math.Abs(n); //make sure it is positive. + n -= (int)n; //remove the integer part of the number. + var decimalPlaces = 0; + + while (n > 0) + { + decimalPlaces++; + n *= 10; + n -= (int)n; + } + return decimalPlaces; + } + + public static int GetDecimalPlaces(string n) + { + return n.Split('.')[1].Length - 1; + } + } +} diff --git a/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs b/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs new file mode 100644 index 0000000..d8cd08a --- /dev/null +++ b/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Http; +using System.Net; +using System.Text.Json; + +namespace Managing.Api.WorkersExceptions; + +public class GlobalErrorHandlingMiddleware +{ + private readonly RequestDelegate _next; + public GlobalErrorHandlingMiddleware(RequestDelegate next) + { + _next = next; + } + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + private static Task HandleExceptionAsync(HttpContext context, Exception exception) + { + HttpStatusCode status; + var exceptionType = exception.GetType(); + + if (exceptionType == typeof(Exception)) + { + status = HttpStatusCode.InternalServerError; + } + else if (exceptionType == typeof(NotImplementedException)) + { + status = HttpStatusCode.NotImplemented; + } + else if (exceptionType == typeof(UnauthorizedAccessException)) + { + status = HttpStatusCode.Unauthorized; + } + else if (exceptionType == typeof(ArgumentException)) + { + status = HttpStatusCode.Unauthorized; + } + else if (exceptionType == typeof(KeyNotFoundException)) + { + status = HttpStatusCode.Unauthorized; + } + else + { + status = HttpStatusCode.InternalServerError; + } + + var message = exception.Message; + var stackTrace = exception.StackTrace; + var exceptionResult = JsonSerializer.Serialize(new { error = message, stackTrace }); + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)status; + return context.Response.WriteAsync(exceptionResult); + } +} diff --git a/src/Managing.Core/MiscExtensions.cs b/src/Managing.Core/MiscExtensions.cs new file mode 100644 index 0000000..45bccf5 --- /dev/null +++ b/src/Managing.Core/MiscExtensions.cs @@ -0,0 +1,66 @@ +namespace Managing.Core +{ + public static class MiscExtensions + { + // Ex: collection.TakeLast(5); + //public static IEnumerable TakeLast(this IEnumerable source, int N) + //{ + // return source.Skip(Math.Max(0, source.Count() - N)); + //} + + // foreach(IEnumerable batch in users.Batch(1000)) + public static IEnumerable> Batch( + this IEnumerable source, int size) + { + T[] bucket = null; + var count = 0; + + foreach (var item in source) + { + if (bucket == null) + bucket = new T[size]; + + bucket[count++] = item; + + if (count != size) + continue; + + yield return bucket.Select(x => x); + + bucket = null; + count = 0; + } + + // Return the last bucket with all remaining elements + if (bucket != null && count > 0) + { + Array.Resize(ref bucket, count); + yield return bucket.Select(x => x); + } + } + + public static void AddItem(this List list, T item) + { + if (!list.Contains(item)) + { + list.Add(item); + } + } + + public static T ParseEnum(string value) + { + return (T)Enum.Parse(typeof(T), value, true); + } + + public static string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + public static string Base64Decode(string base64EncodedData) + { + var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + } +} diff --git a/src/Managing.Core/ValueObject.cs b/src/Managing.Core/ValueObject.cs new file mode 100644 index 0000000..57dac37 --- /dev/null +++ b/src/Managing.Core/ValueObject.cs @@ -0,0 +1,40 @@ +namespace Managing.Core +{ + public abstract class ValueObject + { + protected static bool EqualOperator(ValueObject left, ValueObject right) + { + if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) + { + return false; + } + return ReferenceEquals(left, null) || left.Equals(right); + } + + protected static bool NotEqualOperator(ValueObject left, ValueObject right) + { + return !(EqualOperator(left, right)); + } + + protected abstract IEnumerable GetEqualityComponents(); + + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != GetType()) + { + return false; + } + + var other = (ValueObject)obj; + + return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + + public override int GetHashCode() + { + return GetEqualityComponents() + .Select(x => x != null ? x.GetHashCode() : 0) + .Aggregate((x, y) => x ^ y); + } + } +} diff --git a/src/Managing.Docker/.dockerignore b/src/Managing.Docker/.dockerignore new file mode 100644 index 0000000..c7ef99f --- /dev/null +++ b/src/Managing.Docker/.dockerignore @@ -0,0 +1,29 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +**/Managing.WebApp/**/* +**/Managing.WebApp/ +Managing.WebApp/**/* +Managing.WebApp/ +LICENSE +README.md \ No newline at end of file diff --git a/src/Managing.Docker/docker-compose.sandbox.yml b/src/Managing.Docker/docker-compose.sandbox.yml new file mode 100644 index 0000000..d4c7d4e --- /dev/null +++ b/src/Managing.Docker/docker-compose.sandbox.yml @@ -0,0 +1,67 @@ +version: '3.4' + + +services: + managing.api: + environment: + - ASPNETCORE_ENVIRONMENT=oda-docker + - ASPNETCORE_URLS=https://+:443;http://+:80 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx + ports: + - "80:80" + - "443:443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro + depends_on: + - managingdb + + managing.api.workers: + environment: + - ASPNETCORE_ENVIRONMENT=oda-docker + - ASPNETCORE_URLS=https://+:443;http://+:80 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx + ports: + - "81:80" + - "444:443" + volumes: + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro + depends_on: + - managingdb + + managingdb: + restart: always + volumes: + - mongodata:/data/db + ports: + - "27017:27017" + + elasticsearch: + ports: + - 9200:9200 + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + environment: + - discovery.type=single-node + - xpack.monitoring.templates.enabled=true + - ES_JAVA_OPTS=-Xms1g -Xmx1g + - xpack.security.enabled=false + + kibana: + ports: + - 5601:5601 + depends_on: + - elasticsearch + environment: + - ELASTICSEARCH_URL=http://elasticsearch:9200 + + influxdb: + image: influxdb:latest + volumes: + - influxdata:/var/lib/influxdb2 + ports: + - 8086:8086 + restart: always + diff --git a/src/Managing.Docker/docker-compose.yml b/src/Managing.Docker/docker-compose.yml new file mode 100644 index 0000000..f86658c --- /dev/null +++ b/src/Managing.Docker/docker-compose.yml @@ -0,0 +1,46 @@ +version: '3.4' + +services: + managingdb: + image: mongo + networks: + - managing-network + + managing.api: + image: ${DOCKER_REGISTRY-}managingapi + build: + context: ../. + dockerfile: Managing.Api/Dockerfile + networks: + - managing-network + + managing.api.workers: + image: ${DOCKER_REGISTRY-}managingapiworkers + build: + context: ../. + dockerfile: Managing.Api.Workers/Dockerfile + networks: + - managing-network + + elasticsearch: + image: elasticsearch:8.4.1 + networks: + - managing-network + + kibana: + image: kibana:8.4.1 + + influxdb: + image: influxdb:latest + networks: + - managing-network + +volumes: + elasticsearch-data: + driver: local + mongodata: {} + influxdata: {} + +networks: + managing-network: + name: managing-network \ No newline at end of file diff --git a/src/Managing.Domain.Workers/Managing.Domain.Workers.csproj b/src/Managing.Domain.Workers/Managing.Domain.Workers.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/src/Managing.Domain.Workers/Managing.Domain.Workers.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/src/Managing.Domain.Workers/Worker.cs b/src/Managing.Domain.Workers/Worker.cs new file mode 100644 index 0000000..fc4efe7 --- /dev/null +++ b/src/Managing.Domain.Workers/Worker.cs @@ -0,0 +1,7 @@ +namespace Managing.Domain.Workers +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Accounts/Account.cs b/src/Managing.Domain/Accounts/Account.cs new file mode 100644 index 0000000..1c5b042 --- /dev/null +++ b/src/Managing.Domain/Accounts/Account.cs @@ -0,0 +1,19 @@ +using Managing.Domain.Users; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +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; } + public string Key { get; set; } + public string Secret { get; set; } + public User User { get; set; } + public List Balances { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Accounts/Balance.cs b/src/Managing.Domain/Accounts/Balance.cs new file mode 100644 index 0000000..e066ee0 --- /dev/null +++ b/src/Managing.Domain/Accounts/Balance.cs @@ -0,0 +1,14 @@ +using Managing.Domain.Evm; + +namespace Managing.Domain.Accounts; + +public class Balance +{ + public string TokenImage { get; set; } + public string TokenName { get; set; } + public decimal Amount { get; set; } + public decimal Price { get; set; } + public decimal Value { get; set; } + public string TokenAdress { get; set; } + public Chain Chain { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Backtests/Backtest.cs b/src/Managing.Domain/Backtests/Backtest.cs new file mode 100644 index 0000000..f981ba5 --- /dev/null +++ b/src/Managing.Domain/Backtests/Backtest.cs @@ -0,0 +1,74 @@ +using Exilion.TradingAtomics; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Backtests; + +public class Backtest +{ + public Backtest( + Ticker ticker, + string scenario, + List positions, + List signals, + Timeframe timeframe, + List candles, + BotType botType, + string accountName) + { + Ticker = ticker; + Positions = positions; + Signals = signals; + Timeframe = timeframe; + Candles = candles; + Scenario = scenario; + BotType = botType; + AccountName = accountName; + } + + [Required] + public string Id { get; set; } + [Required] + public decimal FinalPnl { get; set; } + [Required] + public int WinRate { get; set; } + [Required] + public decimal GrowthPercentage { get; set; } + [Required] + public decimal HodlPercentage { get; set; } + [Required] + public Ticker Ticker { get; } + [Required] + public string Scenario { get; set; } + [Required] + public List Positions { get; } + [Required] + public List Signals { get; } + [Required] + public Timeframe Timeframe { get; } + [Required] + public BotType BotType { get; } + [Required] + public string AccountName { get; } + [Required] + public List Candles { get; } + [Required] + public PerformanceMetrics Statistics { get; set; } + [Required] + public decimal Fees { get; set; } + [Required] + public List> WalletBalances { get; set; } + [Required] + public MoneyManagement OptimizedMoneyManagement { get; set; } + [Required] + public MoneyManagement MoneyManagement { get; set; } + + public string GetStringReport() + { + return $"{Ticker} | {Timeframe} | Positions: {Positions.Count} | Winrate: {WinRate}% | Pnl: {FinalPnl:#.##}$ | %Pnl: {GrowthPercentage:#.##}% | %Hodl: {HodlPercentage:#.##}%"; + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs new file mode 100644 index 0000000..ca4af2a --- /dev/null +++ b/src/Managing.Domain/Bots/Bot.cs @@ -0,0 +1,75 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Bots +{ + /// + /// A bot define what code should be run. + /// To run a code you have to herit from this class and implement the Run() method + /// + public abstract class Bot : IBot + { + public int ExecutionCount; + public string Identifier { get; set; } + public string Name { get; set; } + public int Interval { get; set; } + public BotStatus Status { get; set; } + private CancellationTokenSource CancellationToken { get; set; } + + public Bot(string name) + { + Identifier = $"{name}-{DateTime.Now:yyyyMMdd-hhmm}-{Guid.NewGuid()}"; + Name = name; + Status = BotStatus.Down; + CancellationToken = new CancellationTokenSource(); + ExecutionCount = 0; + } + + public virtual void Start() + { + Status = BotStatus.Up; + } + + public async Task InitWorker(Func action) + { + await Task.Run(async () => + { + while (Status == BotStatus.Up) + { + try + { + await action(); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + ExecutionCount++; + await Task.Delay(Interval, CancellationToken.Token); + if (CancellationToken.IsCancellationRequested) + break; + } + }, CancellationToken.Token); + } + + public void Stop() + { + Status = BotStatus.Down; + //CancellationToken.Cancel(); + } + + public void Restart() + { + Status = BotStatus.Up; + } + + public string GetStatus() + { + return Status.ToString(); + } + + public string GetName() + { + return Name; + } + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/IBot.cs b/src/Managing.Domain/Bots/IBot.cs new file mode 100644 index 0000000..7572a3d --- /dev/null +++ b/src/Managing.Domain/Bots/IBot.cs @@ -0,0 +1,12 @@ +namespace Managing.Domain.Bots +{ + public interface IBot + { + string Name { get; set; } + void Start(); + void Stop(); + void Restart(); + string GetStatus(); + string GetName(); + } +} diff --git a/src/Managing.Domain/Candles/Candle.cs b/src/Managing.Domain/Candles/Candle.cs new file mode 100644 index 0000000..c859d95 --- /dev/null +++ b/src/Managing.Domain/Candles/Candle.cs @@ -0,0 +1,34 @@ +using Managing.Common; +using Skender.Stock.Indicators; +using System.ComponentModel.DataAnnotations; + +namespace Managing.Domain.Candles +{ + public class Candle : IQuote + { + [Required] + public Enums.TradingExchanges Exchange { get; set; } + [Required] + public string Ticker { get; set; } + [Required] + public DateTime OpenTime { get; set; } + [Required] + public DateTime Date { get; set; } + [Required] + public decimal Open { get; set; } + [Required] + public decimal Close { get; set; } + public decimal Volume { get; } + [Required] + public decimal High { get; set; } + [Required] + 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; } + [Required] + public Enums.Timeframe Timeframe { get; set; } + } +} diff --git a/src/Managing.Domain/Candles/CandleExtensions.cs b/src/Managing.Domain/Candles/CandleExtensions.cs new file mode 100644 index 0000000..f33227a --- /dev/null +++ b/src/Managing.Domain/Candles/CandleExtensions.cs @@ -0,0 +1,71 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Candles; + +public static class CandleExtensions +{ + + public static Candle SetupClosingCandle(this Candle candle) + { + + return candle; + } + + public static DateTime GetBotPreloadSinceFromTimeframe(Timeframe timeframe) + { + return timeframe switch + { + Timeframe.FiveMinutes => DateTime.UtcNow.AddDays(-1), + Timeframe.FifteenMinutes => DateTime.UtcNow.AddDays(-5), + Timeframe.ThirtyMinutes => DateTime.UtcNow.AddDays(-10), + Timeframe.OneHour => DateTime.UtcNow.AddDays(-30), + Timeframe.FourHour => DateTime.UtcNow.AddDays(-60), + Timeframe.OneDay => DateTime.UtcNow.AddDays(-360), + _ => DateTime.Now.AddHours(-15), + }; + } + + public static DateTime GetPreloadSinceFromTimeframe(Timeframe timeframe) + { + return DateTime.UtcNow.AddDays(GetMinimalDays(timeframe)); + } + + public static int GetIntervalFromTimeframe(Timeframe timeframe) + { + return timeframe switch + { + Timeframe.OneDay => 3600000, // 1h + Timeframe.FiveMinutes => 120000, // 2min + Timeframe.FifteenMinutes => 60000, // 1 min + Timeframe.OneHour => 900000, // 15min + _ => 300000, // 5min + }; + } + + public static int GetUnixInterval(this Timeframe timeframe) + { + return timeframe switch + { + Timeframe.OneDay => 86400, + Timeframe.FiveMinutes => 300, + Timeframe.FifteenMinutes => 900, + Timeframe.FourHour => 14400, + Timeframe.OneHour => 3600, + _ => throw new NotImplementedException() + }; + } + + public static double GetMinimalDays(Timeframe timeframe) + { + return timeframe switch + { + Timeframe.FiveMinutes => -1, + Timeframe.FifteenMinutes => -5, + Timeframe.ThirtyMinutes => -10, + Timeframe.OneHour => -30, + Timeframe.FourHour => -60, + Timeframe.OneDay => -360, + _ => throw new NotImplementedException() + }; + } +} diff --git a/src/Managing.Domain/Evm/Chain.cs b/src/Managing.Domain/Evm/Chain.cs new file mode 100644 index 0000000..4abe622 --- /dev/null +++ b/src/Managing.Domain/Evm/Chain.cs @@ -0,0 +1,8 @@ +namespace Managing.Domain.Evm; + +public class Chain +{ + public string Id { get; set; } + public string RpcUrl { get; set; } + public string Name { get; set; } +} diff --git a/src/Managing.Domain/Evm/EvmBalance.cs b/src/Managing.Domain/Evm/EvmBalance.cs new file mode 100644 index 0000000..d31ba18 --- /dev/null +++ b/src/Managing.Domain/Evm/EvmBalance.cs @@ -0,0 +1,12 @@ +namespace Managing.Domain.Evm; + +public class EvmBalance +{ + public string TokenImage { get; set; } + public string TokenName { get; set; } + public decimal Balance { get; set; } + public decimal Price { get; set; } + public decimal Value { get; set; } + public string TokenAddress { get; set; } + public Chain Chain { get; set; } +} diff --git a/src/Managing.Domain/Evm/Holder.cs b/src/Managing.Domain/Evm/Holder.cs new file mode 100644 index 0000000..4fdad02 --- /dev/null +++ b/src/Managing.Domain/Evm/Holder.cs @@ -0,0 +1,14 @@ +namespace Managing.Domain.Evm; + +public class Holder +{ + public Holder(string holderAddress) + { + HolderAddress = holderAddress; + Nfts = new List(); + } + + public string HolderAddress { get; set; } + public decimal Yield { get; set; } + public List Nfts { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Evm/HolderExtensions.cs b/src/Managing.Domain/Evm/HolderExtensions.cs new file mode 100644 index 0000000..32b358e --- /dev/null +++ b/src/Managing.Domain/Evm/HolderExtensions.cs @@ -0,0 +1,31 @@ +namespace Managing.Domain.Evm; + + +public static class HolderExtensions +{ + public static void AddNft(this Holder holder, Nft nft) + { + if (holder.Nfts.FirstOrDefault(n => n.TokenId == nft.TokenId) == null) + { + holder.Nfts.Add(nft); + } + } + + public static List GetYields(this List holders, decimal yieldAmount) + { + var nftHeldCount = 0; + foreach (var holder in holders) + { + nftHeldCount += holder.Nfts.Count; + } + + var yieldPerNft = yieldAmount / nftHeldCount; + + foreach (var holder in holders) + { + holder.Yield = holder.Nfts.Count * yieldPerNft; + } + + return holders; + } +} diff --git a/src/Managing.Domain/Evm/Nft.cs b/src/Managing.Domain/Evm/Nft.cs new file mode 100644 index 0000000..0484eae --- /dev/null +++ b/src/Managing.Domain/Evm/Nft.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace Managing.Domain.Evm; + +public class Nft +{ + public Nft(string contractAddress, BigInteger tokenId, DateTime blockDate) + { + ContractAddress = contractAddress; + TokenId = tokenId; + BlockDate = blockDate; + } + + public string ContractAddress { get; set; } + public BigInteger TokenId { get; set; } + public DateTime BlockDate { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Evm/Subgraph.cs b/src/Managing.Domain/Evm/Subgraph.cs new file mode 100644 index 0000000..bed0b49 --- /dev/null +++ b/src/Managing.Domain/Evm/Subgraph.cs @@ -0,0 +1,9 @@ +using Managing.Common; + +namespace Managing.Domain.Evm; + +public class Subgraph +{ + public Enums.SubgraphProvider SubgraphProvider { get; set; } + public string Url { get; set; } +} diff --git a/src/Managing.Domain/Managing.Domain.csproj b/src/Managing.Domain/Managing.Domain.csproj new file mode 100644 index 0000000..7b6d9e0 --- /dev/null +++ b/src/Managing.Domain/Managing.Domain.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + diff --git a/src/Managing.Domain/MoneyManagements/MoneyManagement.cs b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs new file mode 100644 index 0000000..4f6428a --- /dev/null +++ b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.MoneyManagements +{ + public class MoneyManagement + { + [Required] + public string Name { get; set; } + [Required] + public Timeframe Timeframe { get; set; } + [Required] + public decimal BalanceAtRisk { get; set; } + [Required] + public decimal StopLoss { get; set; } + [Required] + public decimal TakeProfit { get; set; } + [Required] + public decimal Leverage { get; set; } + + public void FormatPercentage() + { + StopLoss /= 100; + TakeProfit /= 100; + BalanceAtRisk /= 100; + } + } +} diff --git a/src/Managing.Domain/Scenarios/Scenario.cs b/src/Managing.Domain/Scenarios/Scenario.cs new file mode 100644 index 0000000..bd9cfee --- /dev/null +++ b/src/Managing.Domain/Scenarios/Scenario.cs @@ -0,0 +1,20 @@ +using Managing.Domain.Strategies; + +namespace Managing.Domain.Scenarios +{ + public class Scenario + { + public Scenario(string name) + { + Name = name; + Strategies = new List(); + } + + public string Name { get; set; } + public List Strategies { get; set; } + public void AddStrategy(Strategy strategy) + { + Strategies.Add(strategy); + } + } +} diff --git a/src/Managing.Domain/Scenarios/ScenarioHelpers.cs b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs new file mode 100644 index 0000000..32aad83 --- /dev/null +++ b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs @@ -0,0 +1,148 @@ +using Managing.Application.Strategies; +using Managing.Domain.Strategies; +using static Managing.Common.Enums; + +namespace Managing.Domain.Scenarios; + +public static class ScenarioHelpers +{ + public static IEnumerable GetStrategiesFromScenario(Scenario scenario) + { + var strategies = new List(); + foreach (var strategy in scenario.Strategies) + { + IStrategy result = strategy.Type switch + { + StrategyType.StDev => new StDevContext(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.RsiDivergence => new RSIDivergenceStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.RsiDivergenceConfirm => new RSIDivergenceConfirmStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.MacdCross => new MACDCrossStrategy(strategy.Name, strategy.Timeframe, strategy.FastPeriods.Value, strategy.SlowPeriods.Value, strategy.SignalPeriods.Value), + StrategyType.EmaCross => new EmaCrossStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.ThreeWhiteSoldiers => new ThreeWhiteSoldiersStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.SuperTrend => new SuperTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.Multiplier.Value), + StrategyType.ChandelierExit => new ChandelierExitStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.Multiplier.Value), + StrategyType.EmaTrend => new EmaTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value), + StrategyType.StochRsiTrend => new StochRsiTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.StochPeriods.Value, strategy.SignalPeriods.Value, strategy.SmoothPeriods.Value), + StrategyType.Stc => new STCStrategy(strategy.Name, strategy.Timeframe, strategy.CyclePeriods.Value, strategy.FastPeriods.Value, strategy.SlowPeriods.Value), + _ => throw new NotImplementedException(), + }; + + strategies.Add(result); + } + + return strategies; + } + + public static Strategy BuildStrategy( + StrategyType type, + Timeframe timeframe, + 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) + { + var strategy = new Strategy(name, timeframe, type); + + switch (type) + { + case StrategyType.RsiDivergence: + case StrategyType.RsiDivergenceConfirm: + case StrategyType.EmaTrend: + case StrategyType.EmaCross: + case StrategyType.StDev: + if (!period.HasValue) + { + throw new Exception($"Missing period for {strategy.Type} strategy type"); + } + else + { + strategy.Period = period.Value; + } + break; + case StrategyType.MacdCross: + if (!fastPeriods.HasValue || !slowPeriods.HasValue || !signalPeriods.HasValue) + { + throw new Exception($"Missing fastPeriods or slowPeriods or signalPeriods, for {strategy.Type} strategy type"); + } + else + { + strategy.FastPeriods = fastPeriods; + strategy.SlowPeriods = slowPeriods; + strategy.SignalPeriods = signalPeriods; + } + break; + break; + case StrategyType.ThreeWhiteSoldiers: + break; + case StrategyType.SuperTrend: + case StrategyType.ChandelierExit: + if (!period.HasValue || !multiplier.HasValue) + { + throw new Exception($"Missing period or multiplier, for {strategy.Type} strategy type"); + } + else + { + strategy.Period = period; + strategy.Multiplier = multiplier; + } + break; + case StrategyType.StochRsiTrend: + if (!period.HasValue + || !stochPeriods.HasValue + || !signalPeriods.HasValue + || !smoothPeriods.HasValue) + { + throw new Exception($"Missing period, stochPeriods, signalPeriods, smoothPeriods for {strategy.Type} strategy type"); + } + else + { + strategy.Period = period; + strategy.StochPeriods = stochPeriods; + strategy.SignalPeriods = signalPeriods; + strategy.SmoothPeriods = smoothPeriods; + } + break; + case StrategyType.Stc: + if (!fastPeriods.HasValue || !slowPeriods.HasValue || !cyclePeriods.HasValue) + { + throw new Exception($"Missing fastPeriods or slowPeriods or cyclePeriods, for {strategy.Type} strategy type"); + } + else + { + strategy.FastPeriods = fastPeriods; + strategy.SlowPeriods = slowPeriods; + strategy.CyclePeriods = cyclePeriods; + } + break; + default: + break; + } + + return strategy; + } + + public static SignalType GetSignalType(StrategyType type) + { + return type switch + { + StrategyType.RsiDivergence => SignalType.Signal, + StrategyType.RsiDivergenceConfirm => SignalType.Signal, + StrategyType.MacdCross => SignalType.Signal, + StrategyType.EmaCross => SignalType.Signal, + StrategyType.ThreeWhiteSoldiers => SignalType.Signal, + StrategyType.SuperTrend => SignalType.Signal, + StrategyType.ChandelierExit => SignalType.Signal, + StrategyType.EmaTrend => SignalType.Trend, + StrategyType.Composite => SignalType.Signal, + StrategyType.StochRsiTrend => SignalType.Trend, + StrategyType.Stc => SignalType.Signal, + StrategyType.StDev => SignalType.Context, + _ => throw new NotImplementedException(), + }; + } +} diff --git a/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs new file mode 100644 index 0000000..53d839e --- /dev/null +++ b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs @@ -0,0 +1,52 @@ +using Managing.Domain.MoneyManagements; +using static Managing.Common.Enums; + +namespace Managing.Domain.Shared.Helpers +{ + public static class RiskHelpers + { + public static decimal GetStopLossPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement) + { + return direction == TradeDirection.Long ? + price -= price * moneyManagement.StopLoss : + price += price * moneyManagement.StopLoss; + } + + public static decimal GetBalanceAtRisk(decimal balance, MoneyManagement moneyManagement) + { + decimal amountToRisk = balance * moneyManagement.BalanceAtRisk; + + if (amountToRisk <= 1) + { + throw new Exception("Cannot open trade, not enough balance"); + } + + return amountToRisk; + } + + public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1) + { + decimal percentage = moneyManagement.TakeProfit * count; + + price = direction == TradeDirection.Short ? price -= price * percentage : price += price * percentage; + return price; + } + + public static RiskLevel GetRiskFromConfidence(Confidence confidence) + { + switch (confidence) + { + case Confidence.Low: + return RiskLevel.Low; + case Confidence.Medium: + return RiskLevel.Medium; + case Confidence.High: + return RiskLevel.High; + case Confidence.None: + break; + } + + return RiskLevel.Low; + } + } +} diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs new file mode 100644 index 0000000..643f25b --- /dev/null +++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs @@ -0,0 +1,161 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Domain.Shared.Helpers; + +public static class TradingBox +{ + public static Signal GetSignal(HashSet newCandles, HashSet strategies, HashSet previousSignal) + { + var signalOnCandles = new HashSet(); + foreach (var strategy in strategies) + { + strategy.UpdateCandles(newCandles); + var signals = strategy.Run(); + + if (signals == null || signals.Count == 0) continue; + + foreach (var signal in signals.Where(s => s.Date == newCandles.Last().Date)) + { + if (previousSignal.SingleOrDefault(s => s.Identifier == signal.Identifier) == null) + { + if (previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date) + { + signalOnCandles.Add(signal); + } + } + } + } + + if (signalOnCandles.Count != strategies.Count) + return null; + + var data = newCandles.First(); + return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum(data.Ticker), data.Timeframe); + } + + public static Signal ComputeSignals(HashSet strategies, HashSet signalOnCandles, Ticker ticker, Timeframe timeframe) + { + Signal signal = null; + if (strategies.Count > 1) + { + var trendSignal = signalOnCandles.Where(s => s.SignalType == SignalType.Trend); + var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal); + var contextStrategiesCount = strategies.Count(s => s.SignalType == SignalType.Context); + var validContext = true; + + if (contextStrategiesCount > 0 && + signalOnCandles.Count(s => s.SignalType == SignalType.Context) != contextStrategiesCount) + { + validContext = false; + } + + if (signals.All(s => s.Direction == TradeDirection.Long) && trendSignal.All(t => t.Direction == TradeDirection.Long) && validContext) + { + signal = new Signal( + ticker, + TradeDirection.Long, + Confidence.High, + signals.Last().Candle, + signals.Last().Date, + signals.Last().Exchange, + timeframe, + StrategyType.Composite, SignalType.Signal); + } + else if (signals.All(s => s.Direction == TradeDirection.Short) && trendSignal.All(t => t.Direction == TradeDirection.Short) && validContext) + { + signal = new Signal( + ticker, + TradeDirection.Short, + Confidence.High, + signals.Last().Candle, + signals.Last().Date, + signals.Last().Exchange, + timeframe, + StrategyType.Composite, SignalType.Signal); + } + } + else + { + // Only one strategy, we just add the single signal to the bot + signal = signalOnCandles.Single(); + } + + return signal; + } + + public static MoneyManagement GetBestMoneyManagement(List candles, List positions, MoneyManagement originMoneyManagement) + { + // Foreach positions, identitify the price when the position is open + // Then, foreach candles, get the maximum price before the next position + // Then, identify the lowest price before the maximum price + // Base on that, return the best StopLoss and TakeProfit to use and build a + var moneyManagement = new MoneyManagement(); + var stoplossPercentage = new List(); + var takeProfitsPercentage = new List(); + + if (positions.Count == 0) + return null; + + for (var i = 0; i < positions.Count; i++) + { + var position = positions[i]; + var nextPosition = i + 1 < positions.Count ? positions[i + 1] : null; + var (stopLoss, takeProfit) = GetBestSLTPForPosition(candles, position, nextPosition); + + stoplossPercentage.Add(stopLoss); + takeProfitsPercentage.Add(takeProfit); + } + + moneyManagement.StopLoss = stoplossPercentage.Average(); + moneyManagement.TakeProfit = takeProfitsPercentage.Average(); + moneyManagement.BalanceAtRisk = originMoneyManagement.BalanceAtRisk * 100; + moneyManagement.Timeframe = originMoneyManagement.Timeframe; + moneyManagement.Leverage = originMoneyManagement.Leverage; + moneyManagement.Name = "Optimized"; + return moneyManagement; + } + + public static (decimal Stoploss, decimal TakeProfit) GetBestSLTPForPosition(List candles, Position position, Position nextPosition) + { + var stopLoss = 0M; + var takeProfit = 0M; + var candlesBeforeNextPosition = candles.Where(c => c.Date >= position.Date && c.Date <= (nextPosition == null ? candles.Last().Date : nextPosition.Date)); + + if (position.OriginDirection == TradeDirection.Long) + { + var maxPrice = candlesBeforeNextPosition.Max(c => c.High); + var minPrice = candlesBeforeNextPosition.TakeWhile(c => c.High <= maxPrice).Min(c => c.Low); + stopLoss = GetPercentageFromEntry(position.Open.Price, minPrice); + takeProfit = GetPercentageFromEntry(position.Open.Price, maxPrice); + } + else if (position.OriginDirection == TradeDirection.Short) + { + var minPrice = candlesBeforeNextPosition.Min(c => c.Low); + var maxPrice = candlesBeforeNextPosition.TakeWhile(c => c.Low >= minPrice).Max(c => c.High); + stopLoss = GetPercentageFromEntry(position.Open.Price, maxPrice); + takeProfit = GetPercentageFromEntry(position.Open.Price, minPrice); + } + + return (stopLoss, takeProfit); + } + + private static decimal GetPercentageFromEntry(decimal entry, decimal price) + { + return Math.Abs(100 - ((100 * price) / entry)); + } + + public static ProfitAndLoss GetProfitAndLoss(Position position, decimal quantity, decimal price) + { + var orders = new List> + { + new Tuple(position.Open.Quantity, position.Open.Price), + new Tuple(-quantity, price) + }; + return new ProfitAndLoss(orders, position.OriginDirection); + } +} diff --git a/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs b/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs new file mode 100644 index 0000000..c89ba22 --- /dev/null +++ b/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs @@ -0,0 +1,99 @@ +using Exilion.TradingAtomics; +using Managing.Domain.Accounts; +using Managing.Domain.Statistics; +using static Managing.Common.Enums; + +namespace Managing.Domain.Shared.Helpers; + +public static class TradingHelpers +{ + public static decimal GetHodlPercentage(Candles.Candle candle1, Candles.Candle candle2) + { + return candle2.Close * 100 / candle1.Close - 100; + } + + public static decimal GetGrowthFromInitalBalance(decimal balance, decimal finalPnl) + { + var growth = balance + finalPnl; + + return growth * 100 / balance - 100; + } + + public static PerformanceMetrics GetStatistics(Dictionary pnls) + { + var priceSeries = new TimePriceSeries(pnls); + + return priceSeries.CalculatePerformanceMetrics(); + } + + public static decimal GetFeeAmount(decimal fee, decimal amount) + { + return fee * amount; + } + + public static decimal GetFeeAmount(decimal fee, decimal amount, TradingExchanges exchange) + { + if (exchange.Equals(TradingExchanges.Evm)) + return fee; + + return GetFeeAmount(fee, amount); + } + + public static bool IsAGoodTrader(Trader trader) + { + return trader.Winrate > 80 + && trader.TradeCount > 8 + && trader.AverageWin > Math.Abs(trader.AverageLoss) * 3 + && trader.Pnl > 0; + } + + public static bool IsABadTrader(Trader trader) + { + return trader.Winrate < 30 + && trader.TradeCount > 8 + && trader.AverageWin * 3 < Math.Abs(trader.AverageLoss) + && trader.Pnl < 0; + } + + public static List FindBadTrader(this List traders) + { + var filteredTrader = new List(); + foreach (var trader in traders) + { + if (IsABadTrader(trader)) + { + filteredTrader.Add(trader); + } + } + + return filteredTrader; + } + + public static List FindGoodTrader(this List traders) + { + var filteredTrader = new List(); + foreach (var trader in traders) + { + if (IsAGoodTrader(trader)) + { + filteredTrader.Add(trader); + } + } + + return filteredTrader; + } + + public static List MapToTraders(this List accounts) + { + var traders = new List(); + foreach (var account in accounts) + { + traders.Add(new Trader + { + Address = account.Key + }); + } + + return traders; + } +} diff --git a/src/Managing.Domain/Shared/Rules/Check.cs b/src/Managing.Domain/Shared/Rules/Check.cs new file mode 100644 index 0000000..4d27607 --- /dev/null +++ b/src/Managing.Domain/Shared/Rules/Check.cs @@ -0,0 +1,13 @@ +namespace Managing.Domain.Shared.Rules +{ + public static class Check + { + public static void That(IValidationRule rule) + { + if (!rule.IsValid()) + { + throw new RuleException(rule.Message); + } + } + } +} diff --git a/src/Managing.Domain/Shared/Rules/IValidationRules.cs b/src/Managing.Domain/Shared/Rules/IValidationRules.cs new file mode 100644 index 0000000..2c7c4f4 --- /dev/null +++ b/src/Managing.Domain/Shared/Rules/IValidationRules.cs @@ -0,0 +1,9 @@ +namespace Managing.Domain.Shared.Rules +{ + public interface IValidationRule + { + bool IsValid(); + + string Message { get; } + } +} diff --git a/src/Managing.Domain/Shared/Rules/RuleException.cs b/src/Managing.Domain/Shared/Rules/RuleException.cs new file mode 100644 index 0000000..3e4bf5e --- /dev/null +++ b/src/Managing.Domain/Shared/Rules/RuleException.cs @@ -0,0 +1,9 @@ +namespace Managing.Domain.Shared.Rules +{ + public class RuleException : Exception + { + public RuleException(string message) : base(message) + { + } + } +} diff --git a/src/Managing.Domain/Statistics/Spotlight.cs b/src/Managing.Domain/Statistics/Spotlight.cs new file mode 100644 index 0000000..694d8e3 --- /dev/null +++ b/src/Managing.Domain/Statistics/Spotlight.cs @@ -0,0 +1,38 @@ +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +public class SpotlightOverview +{ + [Required] + public List Spotlights { get; set; } + [Required] + public DateTime DateTime { get; set; } + public Guid Identifier { get; set; } + public int ScenarioCount { get; set; } +} + +public class Spotlight +{ + [Required] + public Scenario Scenario { get; set; } + [Required] + public List TickerSignals { get; set; } +} + +public class TickerSignal +{ + [Required] + public Ticker Ticker { get; set; } + [Required] + public List FiveMinutes { get; set; } + [Required] + public List FifteenMinutes { get; set; } + [Required] + public List OneHour { get; set; } + [Required] + public List FourHour { get; set; } + [Required] + public List OneDay { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Statistics/TopVolumeTicker.cs b/src/Managing.Domain/Statistics/TopVolumeTicker.cs new file mode 100644 index 0000000..df18f33 --- /dev/null +++ b/src/Managing.Domain/Statistics/TopVolumeTicker.cs @@ -0,0 +1,12 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Statistics; + +public class TopVolumeTicker +{ +public Ticker Ticker { get; set; } +public DateTime Date { get; set; } +public decimal Volume { get; set; } +public int Rank { get; set; } +public TradingExchanges Exchange { get; set; } +} diff --git a/src/Managing.Domain/Statistics/Trader.cs b/src/Managing.Domain/Statistics/Trader.cs new file mode 100644 index 0000000..7ad671c --- /dev/null +++ b/src/Managing.Domain/Statistics/Trader.cs @@ -0,0 +1,12 @@ +namespace Managing.Domain.Statistics; + +public class Trader +{ + public string Address { get; set; } + public int Winrate { get; set; } + public decimal Pnl { get; set; } + public int TradeCount { get; set; } + public decimal AverageWin { get; set; } + public decimal AverageLoss { get; set; } + public decimal Roi { get; set; } +} diff --git a/src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs b/src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs new file mode 100644 index 0000000..609472d --- /dev/null +++ b/src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs @@ -0,0 +1,39 @@ +using Managing.Common; +using Managing.Domain.Candles; +using Skender.Stock.Indicators; + +namespace Managing.Domain.Strategies.Base; + +public abstract class EmaBaseStrategy : Strategy +{ + protected EmaBaseStrategy(string name, Enums.Timeframe timeframe, Enums.StrategyType type) : base(name, timeframe, type) + { + } + + protected List MapEmaToCandle(List ema, IEnumerable candles) + { + var emaList = new List(); + foreach (var candle in candles) + { + var currentEma = ema.Find(candle.Date); + if (currentEma != null && currentEma.Ema.HasValue) + { + emaList.Add(new CandleEma() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + Ema = currentEma.Ema.Value, + }); + } + } + return emaList; + } + + public class CandleEma : Candle + { + public double Ema { get; set; } + } +} diff --git a/src/Managing.Domain/Strategies/ChandelierExitStrategy.cs b/src/Managing.Domain/Strategies/ChandelierExitStrategy.cs new file mode 100644 index 0000000..822e411 --- /dev/null +++ b/src/Managing.Domain/Strategies/ChandelierExitStrategy.cs @@ -0,0 +1,115 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class ChandelierExitStrategy : Strategy +{ + public List Signals { get; set; } + public ChandelierExitStrategy(string name, Timeframe timeframe, int period, double multiplier) : base(name, timeframe, StrategyType.ChandelierExit) + { + Signals = new List(); + Period = period; + Multiplier = multiplier; + MinimumHistory = 1 + Period.Value; + } + + public override List Run() + { + if (Candles.Count <= MinimumHistory) + { + return null; + } + + try + { + GetSignals(ChandelierType.Long); + GetSignals(ChandelierType.Short); + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private void GetSignals(ChandelierType chandelierType) + { + var chandelier = Candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType).Where(s => s.ChandelierExit.HasValue).ToList(); + var chandelierCandle = MapChandelierToCandle(chandelier, Candles.TakeLast(MinimumHistory)); + var previousCandle = chandelierCandle[0]; + + foreach (var currentCandle in chandelierCandle.Skip(1)) + { + // Short + if (currentCandle.Close < previousCandle.ChandelierExit && + previousCandle.Close > previousCandle.ChandelierExit && + currentCandle.Close < previousCandle.Open && + chandelierType == ChandelierType.Short) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium); + } + + // Long + if (currentCandle.Close > previousCandle.ChandelierExit && + previousCandle.Close < previousCandle.ChandelierExit && + currentCandle.Close > currentCandle.Open && + chandelierType == ChandelierType.Long) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium); + } + + previousCandle = currentCandle; + } + } + + private List MapChandelierToCandle(List superTrend, IEnumerable candles) + { + var superTrends = new List(); + foreach (var candle in candles) + { + var currentChandelier = superTrend.Find(candle.Date); + if (currentChandelier != null) + { + superTrends.Add(new CandleChandelier() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + ChandelierExit = (decimal)currentChandelier.ChandelierExit.Value, + }); + } + } + + return superTrends; + } + + private void AddSignal(CandleChandelier candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal( + MiscExtensions.ParseEnum(candleSignal.Ticker), + direction, + confidence, + candleSignal, + candleSignal.Date, + candleSignal.Exchange, + timeframe, + Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + + private class CandleChandelier : Candle + { + public decimal ChandelierExit { get; internal set; } + } +} diff --git a/src/Managing.Domain/Strategies/EmaCrossStrategy.cs b/src/Managing.Domain/Strategies/EmaCrossStrategy.cs new file mode 100644 index 0000000..f1b7ca0 --- /dev/null +++ b/src/Managing.Domain/Strategies/EmaCrossStrategy.cs @@ -0,0 +1,69 @@ +using Managing.Core; +using Managing.Domain.Shared.Rules; +using Managing.Domain.Strategies; +using Managing.Domain.Strategies.Base; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Application.Strategies; + +public class EmaCrossStrategy : EmaBaseStrategy +{ + public List Signals { get; set; } + + public EmaCrossStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.EmaCross) + { + Signals = new List(); + Period = period; + } + + public override List Run() + { + if (Candles.Count <= Period) + { + return null; + } + + try + { + var ema = Candles.GetEma(Period.Value).ToList(); + var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value)); + + if (ema.Count == 0) + return null; + + var previousCandle = emaCandles[0]; + foreach (var currentCandle in emaCandles.Skip(1)) + { + if (previousCandle.Close > (decimal)currentCandle.Ema && + currentCandle.Close < (decimal)currentCandle.Ema) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium); + } + + if (previousCandle.Close < (decimal)currentCandle.Ema && + currentCandle.Close > (decimal)currentCandle.Ema) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium); + } + + previousCandle = currentCandle; + } + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private void AddSignal(CandleEma candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } +} diff --git a/src/Managing.Domain/Strategies/EmaTrendStrategy.cs b/src/Managing.Domain/Strategies/EmaTrendStrategy.cs new file mode 100644 index 0000000..c9e6052 --- /dev/null +++ b/src/Managing.Domain/Strategies/EmaTrendStrategy.cs @@ -0,0 +1,65 @@ +using Managing.Core; +using Managing.Domain.Shared.Rules; +using Managing.Domain.Strategies.Base; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class EmaTrendStrategy : EmaBaseStrategy +{ + public List Signals { get; set; } + + public EmaTrendStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.EmaTrend) + { + Signals = new List(); + Period = period; + } + + public override List Run() + { + if (Candles.Count <= 2 * Period) + { + return null; + } + + try + { + var ema = Candles.GetEma(Period.Value).ToList(); + var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value)); + + if (ema.Count == 0) + return null; + + var previousCandle = emaCandles[0]; + foreach (var currentCandle in emaCandles.Skip(1)) + { + if (currentCandle.Close > (decimal)currentCandle.Ema) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None); + } + else + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None); + } + + previousCandle = currentCandle; + } + + return Signals.OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + public void AddSignal(CandleEma candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } +} diff --git a/src/Managing.Domain/Strategies/IStrategy.cs b/src/Managing.Domain/Strategies/IStrategy.cs new file mode 100644 index 0000000..384fcdb --- /dev/null +++ b/src/Managing.Domain/Strategies/IStrategy.cs @@ -0,0 +1,20 @@ +using Managing.Domain.Candles; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies +{ + public interface IStrategy + { + string Name { get; set; } + StrategyType Type { get; set; } + SignalType SignalType { get; set; } + int? Period { get; set; } + int? FastPeriods { get; set; } + int? SlowPeriods { get; set; } + int? SignalPeriods { get; set; } + + List Run(); + void UpdateCandles(HashSet newCandles); + string GetName(); + } +} diff --git a/src/Managing.Domain/Strategies/MACDCrossStrategy.cs b/src/Managing.Domain/Strategies/MACDCrossStrategy.cs new file mode 100644 index 0000000..1bdbe56 --- /dev/null +++ b/src/Managing.Domain/Strategies/MACDCrossStrategy.cs @@ -0,0 +1,102 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class MACDCrossStrategy : Strategy +{ + public List Signals { get; set; } + + public MACDCrossStrategy(string name, Timeframe timeframe, int fastPeriods, int slowPeriods, int signalPeriods) : base(name, timeframe, StrategyType.MacdCross) + { + Signals = new List(); + FastPeriods = fastPeriods; + SlowPeriods = slowPeriods; + SignalPeriods = signalPeriods; + } + + public override List Run() + { + if (Candles.Count <= 2*(SlowPeriods + SignalPeriods)) + { + return null; + } + + try + { + var macd = Candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList(); + var macdCandle = MapMacdToCandle(macd, Candles.TakeLast(SignalPeriods.Value)); + + if (macd.Count == 0) + return null; + + var previousCandle = macdCandle[0]; + foreach (var currentCandle in macdCandle.Skip(1)) + { + if (previousCandle.Histogram > 0 && currentCandle.Histogram < 0 && currentCandle.Macd < 0) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium); + } + + if (previousCandle.Histogram < 0 && currentCandle.Histogram > 0 && currentCandle.Macd > 0) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium); + } + + previousCandle = currentCandle; + } + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List MapMacdToCandle(List macd, IEnumerable candles) + { + var macdList = new List(); + foreach (var candle in candles) + { + var currentMacd = macd.Find(candle.Date); + if (currentMacd != null) + { + macdList.Add(new CandleMacd() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + FastEma = currentMacd.FastEma.Value, + SlowEma = currentMacd.SlowEma.Value, + Macd = currentMacd.Macd.Value, + Histogram = currentMacd.Histogram.Value + }); + } + } + return macdList; + } + + private void AddSignal(CandleMacd candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + private class CandleMacd : Candle + { + public double Macd { get; set; } + public double Signal { get; set; } + public double Histogram { get; set; } + public double FastEma { get; set; } + public double SlowEma { get; set; } + } +} diff --git a/src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs b/src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs new file mode 100644 index 0000000..523abb8 --- /dev/null +++ b/src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs @@ -0,0 +1,252 @@ +using Managing.Core; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; +using Candle = Managing.Domain.Candles.Candle; + +namespace Managing.Domain.Strategies; + +public class RSIDivergenceConfirmStrategy : Strategy +{ + public List Signals { get; set; } + + public RSIDivergenceConfirmStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.RsiDivergenceConfirm) + { + Period = period; + Signals = new List(); + } + + /// + /// Get RSI signals + /// + /// + public override List Run() + { + if (Candles.Count <= Period) + { + return null; + } + + var ticker = Candles.First().Ticker; + + try + { + var rsiResult = Candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList(); + var candlesRsi = MapRsiToCandle(rsiResult, Candles.TakeLast(10 * Period.Value)); + + if (candlesRsi.Count(c => c.Rsi > 0) == 0) + return null; + + GetLongSignals(candlesRsi); + GetShortSignals(candlesRsi); + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private void GetLongSignals(List candlesRsi) + { + // Set the low and high for first candle + var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0); + var highPrices = new List(); + var lowPrices = new List(); + + var highRsi = new List(); + var lowRsi = new List(); + + highPrices.Add(firstCandleRsi); + lowPrices.Add(firstCandleRsi); + + highRsi.Add(firstCandleRsi); + lowRsi.Add(firstCandleRsi); + + var previousCandle = firstCandleRsi; + + // For a long + foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1)) + { + // If price go down + if (previousCandle.Close > currentCandle.Close) + { + // because the last price is upper than the current + highPrices.AddItem(previousCandle); + + // Check if rsi is higher than the last lowest + if (currentCandle.Rsi > lowRsi.TakeLast(Period.Value).Min(r => r.Rsi)) + { + // If new higher high, we set it + if (currentCandle.Rsi > highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + + if (currentCandle.Rsi > lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + + // Price go down but RSI go up + if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close)) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None); + } + } + else + { + // No divergence, price go down, rsi go down + lowRsi.AddItem(currentCandle); + } + + lowPrices.AddItem(currentCandle); + } + else + { + // Price go up, so we have to update if price is a new higher high than previous candle + // Normally always true + if (previousCandle.Close < currentCandle.Close) + highPrices.AddItem(currentCandle); //15-15-12-14-17 + + // If rsi is lower low or not set + if (currentCandle.Rsi < lowRsi.Last().Rsi || lowRsi.Last().Rsi == 0) + lowRsi.AddItem(currentCandle); + + // Price going up, so if its a new high we set it + if (currentCandle.Rsi > highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + } + + CheckIfConfimation(currentCandle, TradeDirection.Long); + + previousCandle = currentCandle; + } + } + + private void GetShortSignals(List candlesRsi) + { + // Set the low and high for first candle + var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0); + + var signals = new List(); + var highPrices = new List(); + var lowPrices = new List(); + + var highRsi = new List(); + var lowRsi = new List(); + + highPrices.Add(firstCandleRsi); + lowPrices.Add(firstCandleRsi); + + highRsi.Add(firstCandleRsi); + lowRsi.Add(firstCandleRsi); + + var previousCandle = firstCandleRsi; + + // For a short + foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1)) + { + // If price go up + if (previousCandle.Close < currentCandle.Close) + { + // because the last price is lower than the current + lowPrices.AddItem(previousCandle); + + // Check if rsi is lower than the last high + if (currentCandle.Rsi < highRsi.TakeLast(Period.Value).Max(r => r.Rsi)) + { + // If new lower low, we set it + if (currentCandle.Rsi < lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + + if (currentCandle.Rsi < highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + + // Price go up but RSI go down + if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close)) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None); + } + } + else + { + // No divergence, price go up, rsi go up + highRsi.AddItem(currentCandle); + } + + highPrices.AddItem(currentCandle); + } + else + { + // Price go down, so we have to update if price is a new lower low than previous candle + if (previousCandle.Close > currentCandle.Close) + lowPrices.AddItem(currentCandle); + + // If rsi is higher high or not set + if (currentCandle.Rsi > highRsi.Last().Rsi || highRsi.Last().Rsi == 0) + highRsi.AddItem(currentCandle); + + // Price going down, so if its a new low we set it + if (currentCandle.Rsi < lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + } + + CheckIfConfimation(currentCandle, TradeDirection.Short); + + previousCandle = currentCandle; + } + } + + private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction) + { + var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList(); + var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date + && s.Date < currentCandle.Date + && s.Direction == direction + && s.Confidence == Confidence.None + && s.Status != SignalStatus.Expired + && s.Status != SignalStatus.PositionOpen).ToList(); + + foreach (var signal in signalsOnPeriod) + { + if (direction == TradeDirection.Short && currentCandle.Close < signal.Candle.Open) + { + AddSignal(currentCandle, Timeframe, direction, Confidence.High); + Signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + } + + if (direction == TradeDirection.Long && currentCandle.Close > signal.Candle.Open) + { + AddSignal(currentCandle, Timeframe, direction, Confidence.High); + Signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired; + } + } + } + + private void AddSignal(CandleRsi candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + private List MapRsiToCandle(IReadOnlyCollection rsiResult, + IEnumerable candles) + { + + return candles.Select(c => new CandleRsi() + { + Close = c.Close, + Open = c.Open, + Rsi = rsiResult.Find(c.Date).Rsi.GetValueOrDefault(), + Date = c.Date, + Ticker = c.Ticker, + Exchange = c.Exchange + }).ToList(); + } + + private class CandleRsi : Candle + { + public double Rsi { get; set; } + } +} diff --git a/src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs b/src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs new file mode 100644 index 0000000..4644d1b --- /dev/null +++ b/src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs @@ -0,0 +1,234 @@ +using Managing.Core; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; +using Candle = Managing.Domain.Candles.Candle; + +namespace Managing.Domain.Strategies; + +public class RSIDivergenceStrategy : Strategy +{ + public List Signals { get; set; } + public TradeDirection Direction { get; set; } + private const int UpperBand = 70; + private const int LowerBand = 30; + + public RSIDivergenceStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.RsiDivergence) + { + Period = period; + Signals = new List(); + } + + /// + /// Get RSI signals + /// + /// + public override List Run() + { + if (!Period.HasValue || Candles.Count <= Period) + { + return null; + } + + var ticker = Candles.First().Ticker; + + try + { + var rsiResult = Candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList(); + var candlesRsi = MapRsiToCandle(rsiResult, Candles.TakeLast(10 * Period.Value)); + + if (candlesRsi.Count(c => c.Rsi > 0) == 0) + return null; + + GetLongSignals(candlesRsi); + GetShortSignals(candlesRsi); + + return Signals; + } + catch (RuleException) + { + return null; + } + } + + private void GetLongSignals(List candlesRsi) + { + // Set the low and high for first candle + var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0); + var highPrices = new List(); + var lowPrices = new List(); + + var highRsi = new List(); + var lowRsi = new List(); + + highPrices.Add(firstCandleRsi); + lowPrices.Add(firstCandleRsi); + + highRsi.Add(firstCandleRsi); + lowRsi.Add(firstCandleRsi); + + var previousCandle = firstCandleRsi; + + // For a long + foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1)) + { + // If price go down + if (previousCandle.Close > currentCandle.Close) + { + // because the last price is upper than the current + highPrices.AddItem(previousCandle); + + // Check if rsi is higher than the last lowest + if (currentCandle.Rsi > lowRsi.TakeLast(Period.Value).Min(r => r.Rsi)) + { + // If new higher high, we set it + if (currentCandle.Rsi > highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + + if (currentCandle.Rsi > lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + + // Price go down but RSI go up + if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close)) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long); + } + } + else + { + // No divergence, price go down, rsi go down + lowRsi.AddItem(currentCandle); + } + + lowPrices.AddItem(currentCandle); + } + else + { + // Price go up, so we have to update if price is a new higher high than previous candle + // Normally always true + if (previousCandle.Close < currentCandle.Close) + highPrices.AddItem(currentCandle); //15-15-12-14-17 + + // If rsi is lower low or not set + if (currentCandle.Rsi < lowRsi.Last().Rsi || lowRsi.Last().Rsi == 0) + lowRsi.AddItem(currentCandle); + + // Price going up, so if its a new high we set it + if (currentCandle.Rsi > highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + } + + previousCandle = currentCandle; + } + } + + private void GetShortSignals(List candlesRsi) + { + // Set the low and high for first candle + var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0); + + var signals = new List(); + var highPrices = new List(); + var lowPrices = new List(); + + var highRsi = new List(); + var lowRsi = new List(); + + highPrices.Add(firstCandleRsi); + lowPrices.Add(firstCandleRsi); + + highRsi.Add(firstCandleRsi); + lowRsi.Add(firstCandleRsi); + + var previousCandle = firstCandleRsi; + + // For a short + foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1)) + { + // If price go up + if (previousCandle.Close < currentCandle.Close) + { + // because the last price is lower than the current + lowPrices.AddItem(previousCandle); + + // Check if rsi is lower than the last high + if (currentCandle.Rsi < highRsi.TakeLast(Period.Value).Max(r => r.Rsi)) + { + // If new lower low, we set it + if (currentCandle.Rsi < lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + + if (currentCandle.Rsi < highRsi.Last().Rsi) + highRsi.AddItem(currentCandle); + + // Price go up but RSI go down + if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close)) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short); + } + } + else + { + // No divergence, price go up, rsi go up + highRsi.AddItem(currentCandle); + } + + highPrices.AddItem(currentCandle); + } + else + { + // Price go down, so we have to update if price is a new lower low than previous candle + if (previousCandle.Close > currentCandle.Close) + lowPrices.AddItem(currentCandle); + + // If rsi is higher high or not set + if (currentCandle.Rsi > highRsi.Last().Rsi || highRsi.Last().Rsi == 0) + highRsi.AddItem(currentCandle); + + // Price going down, so if its a new low we set it + if (currentCandle.Rsi < lowRsi.Last().Rsi) + lowRsi.AddItem(currentCandle); + } + + previousCandle = currentCandle; + } + } + + private void AddSignal(CandleRsi candleSignal, Timeframe timeframe, TradeDirection direction) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, Confidence.Low, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType); + + if (Signals.Count(s => s.Identifier == signal.Identifier) < 1) + { + var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList(); + var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date).ToList(); + + if (signalsOnPeriod.Count == 1) + signal.SetConfidence(Confidence.Medium); + + if (signalsOnPeriod.Count >= 2) + signal.SetConfidence(Confidence.High); + + Signals.AddItem(signal); + } + } + + private List MapRsiToCandle(IReadOnlyCollection rsiResult, + IEnumerable candles) + { + + return candles.Select(c => new CandleRsi() + { + Close = c.Close, + Rsi = rsiResult.Find(c.Date).Rsi.GetValueOrDefault(), + Date = c.Date, + Ticker = c.Ticker, + Exchange = c.Exchange + }).ToList(); + } + + private class CandleRsi : Candle + { + public double Rsi { get; set; } + } +} diff --git a/src/Managing.Domain/Strategies/Rules/CloseHigherThanThePreviousHigh.cs b/src/Managing.Domain/Strategies/Rules/CloseHigherThanThePreviousHigh.cs new file mode 100644 index 0000000..c6f11ae --- /dev/null +++ b/src/Managing.Domain/Strategies/Rules/CloseHigherThanThePreviousHigh.cs @@ -0,0 +1,24 @@ +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; + +namespace Managing.Domain.Strategies.Rules +{ + public class CloseHigherThanThePreviousHigh : IValidationRule + { + private readonly Candle _previousCandles; + private readonly Candle _currentCandle; + + public CloseHigherThanThePreviousHigh(Candle previousCandles, Candle currentCandle) + { + _previousCandles = previousCandles; + _currentCandle = currentCandle; + } + + public string Message => $"Current candle did close higher than the previous high close candle"; + + public bool IsValid() + { + return _previousCandles != null ? _currentCandle.Close > _previousCandles.High : false; + } + } +} diff --git a/src/Managing.Domain/Strategies/Rules/CloseLowerThanThePreviousHigh.cs b/src/Managing.Domain/Strategies/Rules/CloseLowerThanThePreviousHigh.cs new file mode 100644 index 0000000..d020b7d --- /dev/null +++ b/src/Managing.Domain/Strategies/Rules/CloseLowerThanThePreviousHigh.cs @@ -0,0 +1,24 @@ +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; + +namespace Managing.Domain.Strategies.Rules +{ + public class CloseLowerThanThePreviousHigh : IValidationRule + { + private readonly Candle _previousCandles; + private readonly Candle _currentCandle; + + public CloseLowerThanThePreviousHigh(Candle previousCandles, Candle currentCandle) + { + _previousCandles = previousCandles; + _currentCandle = currentCandle; + } + + public string Message => $"Current candle did close lower than the previous high close candle"; + + public bool IsValid() + { + return _previousCandles != null ? _currentCandle.Close > _previousCandles.Low : false; + } + } +} \ No newline at end of file diff --git a/src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs b/src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs new file mode 100644 index 0000000..963ce86 --- /dev/null +++ b/src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs @@ -0,0 +1,21 @@ +using Managing.Domain.Shared.Rules; + +namespace Managing.Domain.Strategies.Rules +{ + public class RSIShouldBeBullish : IValidationRule + { + private int _rsiValue; + + public RSIShouldBeBullish(int rsiValue) + { + _rsiValue = rsiValue; + } + + public string Message => $"RSI is not bullish, current value : {_rsiValue}"; + + public bool IsValid() + { + return _rsiValue > 70; + } + } +} diff --git a/src/Managing.Domain/Strategies/STCStrategy.cs b/src/Managing.Domain/Strategies/STCStrategy.cs new file mode 100644 index 0000000..348e6a6 --- /dev/null +++ b/src/Managing.Domain/Strategies/STCStrategy.cs @@ -0,0 +1,103 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class STCStrategy : Strategy +{ + public List Signals { get; set; } + + public STCStrategy(string name, Timeframe timeframe, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name, timeframe, StrategyType.Stc) + { + Signals = new List(); + FastPeriods = fastPeriods; + SlowPeriods = slowPeriods; + CyclePeriods = cyclePeriods; + } + + public override List Run() + { + if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods)) + { + return null; + } + + try + { + var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList(); + var stcCandles = MapStcToCandle(stc, Candles.TakeLast(CyclePeriods.Value)); + + if (stc.Count == 0) + return null; + + var previousCandle = stcCandles[0]; + foreach (var currentCandle in stcCandles.Skip(1)) + { + if (previousCandle.Stc > 75 && currentCandle.Stc <= 75) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium); + } + + if (previousCandle.Stc < 25 && currentCandle.Stc >= 25) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium); + } + + previousCandle = currentCandle; + } + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List MapStcToCandle(List stc, IEnumerable candles) + { + var sctList = new List(); + foreach (var candle in candles) + { + var currentSct = stc.Find(candle.Date); + if (currentSct != null) + { + sctList.Add(new CandleSct() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + Stc = currentSct.Stc + }); + } + } + return sctList; + } + + private void AddSignal(CandleSct candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal( + MiscExtensions.ParseEnum(candleSignal.Ticker), + direction, + confidence, + candleSignal, + candleSignal.Date, + candleSignal.Exchange, + timeframe, + Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + private class CandleSct : Candle + { + public double? Stc { get; internal set; } + } +} diff --git a/src/Managing.Domain/Strategies/Signal.cs b/src/Managing.Domain/Strategies/Signal.cs new file mode 100644 index 0000000..8414cb9 --- /dev/null +++ b/src/Managing.Domain/Strategies/Signal.cs @@ -0,0 +1,62 @@ +using Managing.Core; +using Managing.Domain.Candles; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies +{ + public class Signal : ValueObject + { + [Required] + public SignalStatus Status { get; set; } + [Required] + public TradeDirection Direction { get; } + [Required] + public Confidence Confidence { get; private set; } + [Required] + public Timeframe Timeframe { get; } + [Required] + public DateTime Date { get; private set; } + [Required] + public Candle Candle { get; } + [Required] + public string Identifier { get; } + [Required] + public Ticker Ticker { get; } + [Required] + public TradingExchanges Exchange { get; set; } + [Required] + public StrategyType StrategyType { get; set; } + [Required] + public SignalType SignalType { get; set; } + + public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date, + TradingExchanges exchange, Timeframe timeframe, StrategyType strategyType, SignalType signalType) + { + Direction = direction; + Confidence = confidence; + Candle = candle; + Date = date; + Ticker = ticker; + Exchange = exchange; + Status = SignalStatus.WaitingForPosition; + Timeframe = timeframe; + StrategyType = strategyType; + + Identifier = $"{StrategyType}-{direction}-{ticker}-{Timeframe}-{candle?.Close}-{date:yyyyMMdd-HHmmss}"; + SignalType = signalType; + } + + public void SetConfidence(Confidence confidence) + { + Confidence = confidence; + } + + protected override IEnumerable GetEqualityComponents() + { + yield return Direction; + yield return Confidence; + yield return Date; + } + } +} diff --git a/src/Managing.Domain/Strategies/StDevContext.cs b/src/Managing.Domain/Strategies/StDevContext.cs new file mode 100644 index 0000000..6ea8af7 --- /dev/null +++ b/src/Managing.Domain/Strategies/StDevContext.cs @@ -0,0 +1,102 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class StDevContext : Strategy +{ + public List Signals { get; set; } + + public StDevContext(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.StDev) + { + Signals = new List(); + Period = period; + } + + public override List Run() + { + if (Candles.Count <= Period) + { + return null; + } + + try + { + var stDev = Candles.GetStdDev(Period.Value).ToList(); + var stDevCandles = MapStDev(stDev, Candles.TakeLast(Period.Value)); + + if (stDev.Count == 0) + return null; + + var lastCandle = stDevCandles.Last(); + + if (lastCandle.ZScore is < 1.2 and > (-1.2)) + { + AddSignal(lastCandle, Timeframe, TradeDirection.None, Confidence.Medium); + } + else + { + Console.WriteLine("Bad zscore"); + } + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List MapStDev(List stDev, IEnumerable candles) + { + var sctList = new List(); + foreach (var candle in candles) + { + var currentSct = stDev.Find(candle.Date); + if (currentSct != null) + { + sctList.Add(new CandleStDev() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + StDev = currentSct.StdDev, + ZScore = currentSct.ZScore, + StdDevSma = currentSct.StdDevSma, + Mean = currentSct.Mean + }); + } + } + return sctList; + } + + private void AddSignal(CandleStDev candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal( + MiscExtensions.ParseEnum(candleSignal.Ticker), + direction, + confidence, + candleSignal, + candleSignal.Date, + candleSignal.Exchange, + timeframe, + Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + private class CandleStDev : Candle + { + public double? StDev { get; internal set; } + public double? ZScore { get; internal set; } + public double? StdDevSma { get; internal set; } + public double? Mean { get; internal set; } + } +} diff --git a/src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs b/src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs new file mode 100644 index 0000000..bc591fe --- /dev/null +++ b/src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs @@ -0,0 +1,112 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class StochRsiTrendStrategy : Strategy +{ + public List Signals { get; set; } + + public StochRsiTrendStrategy( + string name, + Timeframe timeframe, + int period, + int stochPeriod, + int signalPeriod, + int smoothPeriods) : base(name, timeframe, StrategyType.StochRsiTrend) + { + Signals = new List(); + StochPeriods = stochPeriod; + SignalPeriods = signalPeriod; + SmoothPeriods = smoothPeriods; + Period = period; + } + + public override List Run() + { + if (Candles.Count <= 10 * Period + 50) + { + return null; + } + + try + { + var stochRsi = Candles.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value).RemoveWarmupPeriods().ToList(); + var stochRsiCandles = MapStochRsiToCandle(stochRsi, Candles.TakeLast(Period.Value)); + + if (stochRsi.Count == 0) + return null; + + var previousCandle = stochRsiCandles[0]; + foreach (var currentCandle in stochRsiCandles.Skip(1)) + { + if (currentCandle.Signal < 20) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None); + } + else if (currentCandle.Signal > 80) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None); + } + + previousCandle = currentCandle; + } + + return Signals.OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List MapStochRsiToCandle(List ema, IEnumerable candles) + { + var emaList = new List(); + foreach (var candle in candles) + { + var currentEma = ema.Find(candle.Date); + if (currentEma != null) + { + emaList.Add(new CandleStochRsi() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + Signal = currentEma.Signal.Value, + StochRsi = currentEma.StochRsi.Value + }); + } + } + return emaList; + } + + private void AddSignal(CandleStochRsi candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal( + MiscExtensions.ParseEnum(candleSignal.Ticker), + direction, + confidence, + candleSignal, + candleSignal.Date, + candleSignal.Exchange, + timeframe, + Type, + SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + private class CandleStochRsi : Candle + { + public double Signal { get; internal set; } + public double StochRsi { get; internal set; } + } +} diff --git a/src/Managing.Domain/Strategies/Strategy.cs b/src/Managing.Domain/Strategies/Strategy.cs new file mode 100644 index 0000000..ec843a3 --- /dev/null +++ b/src/Managing.Domain/Strategies/Strategy.cs @@ -0,0 +1,58 @@ +using Managing.Domain.Candles; +using Managing.Core; +using static Managing.Common.Enums; +using Managing.Domain.Scenarios; + +namespace Managing.Domain.Strategies +{ + public class Strategy : IStrategy + { + public Strategy(string name, Timeframe timeframe, StrategyType type) + { + Name = name; + Timeframe = timeframe; + Candles = new List(); + Type = type; + SignalType = ScenarioHelpers.GetSignalType(type); + } + + public string Name { get; set; } + public Timeframe Timeframe { get; set; } + public List Candles { get; set; } + public StrategyType Type { get; set; } + public SignalType SignalType { get; set; } + public int MinimumHistory { 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? SmoothPeriods { get; set; } + public int? StochPeriods { get; set; } + public int? CyclePeriods { get; set; } + + public virtual List Run() + { + return new List(); + } + + public void UpdateCandles(HashSet newCandles) + { + lock (Candles) + { + foreach (var item in newCandles.ToList()) + { + if (Candles.All(c => c.Date != item.Date)) + { + Candles.AddItem(item); + } + } + } + } + + public string GetName() + { + return Name; + } + } +} diff --git a/src/Managing.Domain/Strategies/SuperTrendStrategy.cs b/src/Managing.Domain/Strategies/SuperTrendStrategy.cs new file mode 100644 index 0000000..bab8f21 --- /dev/null +++ b/src/Managing.Domain/Strategies/SuperTrendStrategy.cs @@ -0,0 +1,104 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Skender.Stock.Indicators; +using static Managing.Common.Enums; + +namespace Managing.Domain.Strategies; + +public class SuperTrendStrategy : Strategy +{ + public List Signals { get; set; } + + public SuperTrendStrategy(string name, Timeframe timeframe, int period, double multiplier) : base(name, timeframe, StrategyType.SuperTrend) + { + Signals = new List(); + Period = period; + Multiplier = multiplier; + MinimumHistory = 100 + Period.Value; + } + + public override List Run() + { + if (Candles.Count <= MinimumHistory) + { + return null; + } + + try + { + var superTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue).ToList(); + var superTrendCandle = MapSuperTrendToCandle(superTrend, Candles.TakeLast(MinimumHistory)); + + if (superTrendCandle.Count == 0) + return null; + + var previousCandle = superTrendCandle[0]; + foreach (var currentCandle in superTrendCandle.Skip(1)) + { + // Short + if (currentCandle.Close < previousCandle.SuperTrend && previousCandle.Close > previousCandle.SuperTrend) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium); + } + + // Long + if (currentCandle.Close > previousCandle.SuperTrend && previousCandle.Close < previousCandle.SuperTrend) + { + AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium); + } + + previousCandle = currentCandle; + } + + return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List MapSuperTrendToCandle(List superTrend, IEnumerable candles) + { + var superTrends = new List(); + foreach (var candle in candles) + { + var currentSuperTrend = superTrend.Find(candle.Date); + if (currentSuperTrend != null) + { + superTrends.Add(new CandleSuperTrend() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + SuperTrend = currentSuperTrend.SuperTrend.Value, + LowerBand = currentSuperTrend.LowerBand, + UpperBand = currentSuperTrend.UpperBand, + }); + } + } + + return superTrends; + } + + private void AddSignal(CandleSuperTrend candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence) + { + var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, + candleSignal.Exchange, timeframe, Type, SignalType); + if (!Signals.Any(s => s.Identifier == signal.Identifier)) + { + Signals.AddItem(signal); + } + } + + + private class CandleSuperTrend : Candle + { + public decimal SuperTrend { get; internal set; } + public decimal? LowerBand { get; internal set; } + public decimal? UpperBand { get; internal set; } + } +} diff --git a/src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs b/src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs new file mode 100644 index 0000000..e8b5606 --- /dev/null +++ b/src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs @@ -0,0 +1,55 @@ +using Managing.Domain.Candles; +using Managing.Domain.Shared.Rules; +using Managing.Domain.Strategies; +using Managing.Domain.Strategies.Rules; +using static Managing.Common.Enums; + +namespace Managing.Application.Strategies +{ + public class ThreeWhiteSoldiersStrategy : Strategy + { + public ThreeWhiteSoldiersStrategy(string name, Timeframe timeframe, int period) + : base(name, timeframe, StrategyType.ThreeWhiteSoldiers) + { + Period = period; + } + + public TradeDirection Direction { get; } + + public override List Run() + { + var signals = new List(); + + if (Candles.Count <= 3) + { + return null; + } + + try + { + var lastFourCandles = Candles.TakeLast(4); + Candle previousCandles = null; + + foreach (var currentCandle in lastFourCandles) + { + if (Direction == TradeDirection.Long) + { + Check.That(new CloseHigherThanThePreviousHigh(previousCandles, currentCandle)); + } + else + { + Check.That(new CloseLowerThanThePreviousHigh(previousCandles, currentCandle)); + } + + previousCandles = currentCandle; + } + + return signals; + } + catch (RuleException) + { + return null; + } + } + } +} diff --git a/src/Managing.Domain/Trades/Fee.cs b/src/Managing.Domain/Trades/Fee.cs new file mode 100644 index 0000000..33c10de --- /dev/null +++ b/src/Managing.Domain/Trades/Fee.cs @@ -0,0 +1,10 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Trades; + +public class Fee +{ + public decimal Cost { get; set; } + public TradingExchanges Exchange { get; set; } + public DateTime LastUpdate { get; set; } +} diff --git a/src/Managing.Domain/Trades/OrderBookEntry.cs b/src/Managing.Domain/Trades/OrderBookEntry.cs new file mode 100644 index 0000000..182e5ec --- /dev/null +++ b/src/Managing.Domain/Trades/OrderBookEntry.cs @@ -0,0 +1,7 @@ +namespace Managing.Domain.Trades; + +public class OrderBookEntry +{ + public decimal Price { get; set; } + public decimal Quantity { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Trades/OrderBookExtensions.cs b/src/Managing.Domain/Trades/OrderBookExtensions.cs new file mode 100644 index 0000000..f482934 --- /dev/null +++ b/src/Managing.Domain/Trades/OrderBookExtensions.cs @@ -0,0 +1,35 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Trades; + +public static class OrderBookExtensions +{ + public static decimal GetBestPrice(this Orderbook orderbook, TradeDirection direction, decimal quantity) + { + var entries = direction == TradeDirection.Long ? orderbook.Asks : orderbook.Bids; + var spend = 0m; + var entryIndex = 0; + var matches = new List<(decimal amount, decimal price, decimal sum)>(); + + while (spend < quantity) + { + if (entries[entryIndex].Quantity > quantity - spend) + { + var amount = quantity - spend; + matches.Add((amount, entries[entryIndex].Price, amount * entries[entryIndex].Price)); + spend += amount; + } + else + { + matches.Add((entries[entryIndex].Quantity, entries[entryIndex].Price, entries[entryIndex].Quantity * entries[entryIndex].Price)); + spend += entries[entryIndex].Quantity; + } + + entryIndex++; + } + + var meanPrice = matches.Sum(s => s.sum) / matches.Sum(s => s.amount); + + return meanPrice; + } +} diff --git a/src/Managing.Domain/Trades/Orderbook.cs b/src/Managing.Domain/Trades/Orderbook.cs new file mode 100644 index 0000000..4f98c8c --- /dev/null +++ b/src/Managing.Domain/Trades/Orderbook.cs @@ -0,0 +1,8 @@ +namespace Managing.Domain.Trades; + +public class Orderbook +{ + public List Bids { get; set; } + public List Asks { get; set; } +} + \ No newline at end of file diff --git a/src/Managing.Domain/Trades/Position.cs b/src/Managing.Domain/Trades/Position.cs new file mode 100644 index 0000000..52dfa74 --- /dev/null +++ b/src/Managing.Domain/Trades/Position.cs @@ -0,0 +1,57 @@ +using Managing.Domain.MoneyManagements; +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Trades +{ + public class Position + { + public Position(string accountName, TradeDirection originDirection, Ticker ticker, MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date) + { + Identifier = Guid.NewGuid().ToString(); + AccountName = accountName; + OriginDirection = originDirection; + Ticker = ticker; + MoneyManagement = moneyManagement; + Initiator = positionInitiator; + Date = date; + Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New; + } + + [Required] + public string AccountName { get; } + [Required] + public DateTime Date { get; set; } + [Required] + public TradeDirection OriginDirection { get; } + [Required] + public Ticker Ticker { get; } + [Required] + public MoneyManagement MoneyManagement { get; } + [Required] + public Trade Open { get; set; } + [Required] + public Trade StopLoss { get; set; } + [Required] + public Trade TakeProfit1 { get; set; } + public Trade TakeProfit2 { get; set; } + public ProfitAndLoss ProfitAndLoss { get; set; } + [Required] + public PositionStatus Status { get; set; } + public string SignalIdentifier { get; set; } + [Required] + public string Identifier { get; set; } + [Required] + public PositionInitiator Initiator { get; } + + public bool IsFinished() + { + return Status switch + { + PositionStatus.Finished => true, + PositionStatus.Flipped => true, + _ => false + }; + } + } +} diff --git a/src/Managing.Domain/Trades/ProfitAndLoss.cs b/src/Managing.Domain/Trades/ProfitAndLoss.cs new file mode 100644 index 0000000..ac98eb9 --- /dev/null +++ b/src/Managing.Domain/Trades/ProfitAndLoss.cs @@ -0,0 +1,102 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Trades +{ + public sealed class ProfitAndLoss + { + public decimal Realized { get; set; } + + public decimal Net { get; set; } + + public decimal AverageOpenPrice { get; private set; } + private const decimal _multiplier = 100000; + + public ProfitAndLoss() + { + } + + public ProfitAndLoss(IEnumerable> initial, TradeDirection direction) + { + decimal buyQuantity = 0, sellQuantity = 0; + decimal averageBuyPrice = 0, averageSellPrice = 0; + foreach (var fill in initial) + { + var quantityFilled = fill.Item1; + if (fill.Item1 > 0) + { + buyQuantity += quantityFilled; + averageBuyPrice += quantityFilled * fill.Item2; + } + else if (fill.Item1 < 0) + { + var absQuantity = Math.Abs(quantityFilled); + sellQuantity += absQuantity; + averageSellPrice += absQuantity * fill.Item2; + } + } + + if (buyQuantity > 0) + averageBuyPrice /= buyQuantity; + if (sellQuantity > 0) + averageSellPrice /= sellQuantity; + + //buyQuantity = (buyQuantity / _multiplier); + //sellQuantity = (sellQuantity / _multiplier); + Net = buyQuantity - sellQuantity; + AverageOpenPrice = Net > 0 ? averageBuyPrice : averageSellPrice; + + var absoluteRealized = (averageSellPrice - averageBuyPrice) * Math.Min(buyQuantity, sellQuantity); + if (direction == TradeDirection.Long) + { + Realized = absoluteRealized; + } else + { + Realized = -absoluteRealized; + } + } + + public void AddFill(decimal quantity, decimal price, TradeDirection direction) + { + if (quantity == 0) + throw new ArgumentOutOfRangeException(nameof(quantity), "Quantity must be non-zero."); + + if (Math.Sign(Net) != Math.Sign(quantity)) + { + decimal absNet = Math.Abs(Net); + decimal absQuantity = Math.Abs(quantity); + decimal realizedResult = 0; + + if (absNet == absQuantity) // flat + { + realizedResult = (price - AverageOpenPrice) * Net; + AverageOpenPrice = 0; + } + else if (absNet > absQuantity) // decrease + { + realizedResult = (price - AverageOpenPrice) * -quantity; + } + else // reverse + { + realizedResult = (price - AverageOpenPrice) * Net; + AverageOpenPrice = price; + } + + if (direction == TradeDirection.Long) + Realized += realizedResult; + else + Realized += -realizedResult; + } + else // increase position + { + AverageOpenPrice = (Net * AverageOpenPrice + quantity * price) / (Net + quantity); + } + + Net += quantity; + } + + public decimal FloatingForTheoriticalExit(decimal exitPrice) + { + return (exitPrice - AverageOpenPrice) * Net; + } + } +} diff --git a/src/Managing.Domain/Trades/Trade.cs b/src/Managing.Domain/Trades/Trade.cs new file mode 100644 index 0000000..3166a82 --- /dev/null +++ b/src/Managing.Domain/Trades/Trade.cs @@ -0,0 +1,69 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Trades +{ + public class Trade + { + public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker, + decimal quantity, decimal price, decimal? leverage, string exchangeOrderId, string message) + { + Date = date; + Direction = direction; + Status = status; + TradeType = tradeType; + Ticker = ticker; + Quantity = quantity; + Price = price; + Leverage = leverage.GetValueOrDefault(); + ExchangeOrderId = exchangeOrderId; + Message = message; + Fee = 0; + } + + public decimal Fee { get; set; } + [Required] + public DateTime Date { get; } + [Required] + public TradeDirection Direction { get; } + [Required] + public TradeStatus Status { get; private set; } + [Required] + public TradeType TradeType { get; } + [Required] + public Ticker Ticker { get; } + [Required] + public decimal Quantity { get; set; } + [Required] + public decimal Price { get; set; } + public decimal Leverage { get; } + [Required] + public string ExchangeOrderId { get; private set; } + public string Message { get; private set; } + + public void SetStatus(TradeStatus status) + { + Status = status; + } + + public void SetExchangeOrderId(string exchangeOrderId) + { + ExchangeOrderId = exchangeOrderId; + } + + public void SetMessage(string message) + { + Message = message; + } + + public void SetQuantity(decimal quantity, int precision) + { + Quantity = Math.Round(quantity, precision); + } + + public void SetPrice(decimal price, int precision) + { + Price = Math.Round(price, precision); + } + } +} diff --git a/src/Managing.Domain/Users/User.cs b/src/Managing.Domain/Users/User.cs new file mode 100644 index 0000000..e27bb5c --- /dev/null +++ b/src/Managing.Domain/Users/User.cs @@ -0,0 +1,9 @@ +using Managing.Domain.Accounts; + +namespace Managing.Domain.Users; + +public class User +{ + public string Name { get; set; } + public List Accounts { get; set; } +} diff --git a/src/Managing.Domain/Workers/Worker.cs b/src/Managing.Domain/Workers/Worker.cs new file mode 100644 index 0000000..6840106 --- /dev/null +++ b/src/Managing.Domain/Workers/Worker.cs @@ -0,0 +1,13 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Workers; + +public class Worker +{ + public WorkerType WorkerType { get; set; } + public DateTime StartTime { get; set; } + public DateTime? LastRunTime { get; set; } + public int ExecutionCount { get; set; } + public TimeSpan Delay { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/Managing.Domain/Workflows/FlowBase.cs b/src/Managing.Domain/Workflows/FlowBase.cs new file mode 100644 index 0000000..c6efc50 --- /dev/null +++ b/src/Managing.Domain/Workflows/FlowBase.cs @@ -0,0 +1,19 @@ +using static Managing.Common.Enums; + +namespace Managing.Domain.Workflows; + +public abstract class FlowBase : IFlow +{ + public abstract Guid Id { get; } + public abstract string Name { get; } + public abstract FlowType Type { get; } + public abstract string Description { get; } + public abstract List AcceptedInputs { get; } + public abstract List Children { get; set; } + public abstract List Parameters { get; set; } + public abstract Guid ParentId { get; } + public abstract string Output { get; set; } + public abstract List OutputTypes { get; } + public abstract Task Execute(string input); + public abstract void MapParameters(); +} diff --git a/src/Managing.Domain/Workflows/FlowParameter.cs b/src/Managing.Domain/Workflows/FlowParameter.cs new file mode 100644 index 0000000..aeff619 --- /dev/null +++ b/src/Managing.Domain/Workflows/FlowParameter.cs @@ -0,0 +1,7 @@ +namespace Managing.Domain.Workflows; + +public class FlowParameter +{ + public dynamic Value { get; set; } + public string Name { get; set; } +} diff --git a/src/Managing.Domain/Workflows/IFlow.cs b/src/Managing.Domain/Workflows/IFlow.cs new file mode 100644 index 0000000..8564bf3 --- /dev/null +++ b/src/Managing.Domain/Workflows/IFlow.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Workflows; + +public interface IFlow +{ + [Required] + Guid Id { get; } + [Required] + string Name { get; } + [Required] + FlowType Type { get; } + [Required] + string Description { get; } + [Required] + List AcceptedInputs { get; } + List Children { get; set; } + [Required] + List Parameters { get; set; } + Guid ParentId { get; } + string Output { get; set; } + [Required] + List OutputTypes { get; } + Task Execute(string input); +} diff --git a/src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs b/src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs new file mode 100644 index 0000000..cf0ec11 --- /dev/null +++ b/src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Workflows.Synthetics; + +public class SyntheticFlow +{ + [Required] + public string Id { get; set; } + public string ParentId { get; set; } + [Required] + public FlowType Type { get; set; } + [Required] + public List Parameters { get; set; } +} diff --git a/src/Managing.Domain/Workflows/Synthetics/SyntheticFlowParameter.cs b/src/Managing.Domain/Workflows/Synthetics/SyntheticFlowParameter.cs new file mode 100644 index 0000000..dd83ff2 --- /dev/null +++ b/src/Managing.Domain/Workflows/Synthetics/SyntheticFlowParameter.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Managing.Domain.Workflows.Synthetics; + +public class SyntheticFlowParameter +{ + [Required] + public string Value { get; set; } + [Required] + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Workflows/Synthetics/SyntheticWorkflow.cs b/src/Managing.Domain/Workflows/Synthetics/SyntheticWorkflow.cs new file mode 100644 index 0000000..3834230 --- /dev/null +++ b/src/Managing.Domain/Workflows/Synthetics/SyntheticWorkflow.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Workflows.Synthetics; + +public class SyntheticWorkflow +{ + [Required] + public string Name { get; set; } + [Required] + public WorkflowUsage Usage { get; set; } + [Required] + public string Description { get; set; } + [Required] + public List Flows { get; set; } +} diff --git a/src/Managing.Domain/Workflows/Workflow.cs b/src/Managing.Domain/Workflows/Workflow.cs new file mode 100644 index 0000000..de5e3be --- /dev/null +++ b/src/Managing.Domain/Workflows/Workflow.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using static Managing.Common.Enums; + +namespace Managing.Domain.Workflows; + +public class Workflow +{ + [Required] + public string Name { get; set; } + [Required] + public WorkflowUsage Usage { get; set; } + [Required] + public List Flows { get; set; } + [Required] + public string Description { get; set; } + + public async Task Execute() + { + foreach (var flow in Flows) + { + await flow.Execute(string.Empty); + } + } +} diff --git a/src/Managing.Infrastructure.Database/AccountRepository.cs b/src/Managing.Infrastructure.Database/AccountRepository.cs new file mode 100644 index 0000000..d76a5b2 --- /dev/null +++ b/src/Managing.Infrastructure.Database/AccountRepository.cs @@ -0,0 +1,46 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Accounts; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class AccountRepository : IAccountRepository +{ + private readonly IMongoRepository _accountRepository; + + public AccountRepository(IMongoRepository accountRepository) + { + _accountRepository = accountRepository; + } + + public void DeleteAccountByName(string name) + { + var account = _accountRepository.FindOne(a => a.Name == name); + _accountRepository.DeleteById(account.Id.ToString()); + } + + public async Task GetAccountByKeyAsync(string key) + { + var account = await _accountRepository.FindOneAsync(a => a.Key == key); + return MongoMappers.Map(account); + } + + public async Task GetAccountByNameAsync(string name) + { + var account = await _accountRepository.FindOneAsync(a => a.Name == name); + return MongoMappers.Map(account); + } + + public IEnumerable GetAccounts() + { + var accounts = _accountRepository.FindAll(); + return MongoMappers.Map(accounts); + } + + public async Task InsertAccountAsync(Account account) + { + await _accountRepository.InsertOneAsync(MongoMappers.Map(account)); + } +} diff --git a/src/Managing.Infrastructure.Database/BacktestRepository.cs b/src/Managing.Infrastructure.Database/BacktestRepository.cs new file mode 100644 index 0000000..101e955 --- /dev/null +++ b/src/Managing.Infrastructure.Database/BacktestRepository.cs @@ -0,0 +1,38 @@ +using Managing.Application.Abstractions; +using Managing.Domain.Backtests; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class BacktestRepository : IBacktestRepository +{ + private readonly IMongoRepository _backtestRepository; + + public BacktestRepository(IMongoRepository backtestRepository) + { + _backtestRepository = backtestRepository; + } + + public void DeleteAllBacktests() + { + _backtestRepository.DropCollection(); + } + + public void DeleteBacktestById(string id) + { + _backtestRepository.DeleteById(id); + } + + public IEnumerable GetBacktests() + { + var backtests = _backtestRepository.FindAll(); + return backtests.Select(b => MongoMappers.Map(b)); + } + + public void InsertBacktest(Backtest backtest) + { + _backtestRepository.InsertOne(MongoMappers.Map(backtest)); + } +} diff --git a/src/Managing.Infrastructure.Database/CandleRepository.cs b/src/Managing.Infrastructure.Database/CandleRepository.cs new file mode 100644 index 0000000..b0547e1 --- /dev/null +++ b/src/Managing.Infrastructure.Database/CandleRepository.cs @@ -0,0 +1,105 @@ +using InfluxDB.Client.Writes; +using Managing.Application.Abstractions.Repositories; +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Infrastructure.Databases.InfluxDb; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases; + +public class CandleRepository : ICandleRepository +{ + private readonly string _priceBucket = "prices-bucket"; + private readonly IInfluxDbRepository _influxDbRepository; + private readonly ILogger _logger; + + public CandleRepository(IInfluxDbRepository influxDbRepository, ILogger logger) + { + _influxDbRepository = influxDbRepository; + _logger = logger; + } + + public async Task> GetCandles( + TradingExchanges exchange, + Ticker ticker, + Timeframe timeframe, + DateTime start) + { + var results = await _influxDbRepository.QueryAsync(async query => + { + var flux = $"from(bucket:\"{_priceBucket}\") " + + $"|> range(start: {start: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(flux, _influxDbRepository.Organization); + return prices.Select(price => PriceHelpers.Map(price)).ToList(); + }); + + return results; + } + + public async Task> GetTickersAsync( + TradingExchanges exchange, + Timeframe timeframe, + DateTime start) + { + var results = await _influxDbRepository.QueryAsync(async query => + { + var flux = $"from(bucket:\"{_priceBucket}\") " + + $"|> range(start: {start:s}Z, stop: now()) " + + $"|> filter(fn: (r) => r[\"_measurement\"] == \"price\")" + + $"|> filter(fn: (r) => r[\"exchange\"] == \"{exchange}\")" + + $"|> filter(fn: (r) => r[\"timeframe\"] == \"{timeframe}\")" + + $"|> keep(columns: [\"ticker\"])" + + $"|> distinct()"; + + var tickers = new List(); + var records = await query.QueryAsync(flux, _influxDbRepository.Organization); + records.ForEach(table => + { + var fluxRecords = table.Records; + fluxRecords.ForEach(fluxRecord => + { + tickers.AddItem(MiscExtensions.ParseEnum(fluxRecord.GetValueByKey("ticker").ToString())); + }); + }); + + return tickers; + }); + + return results; + } + + public void InsertCandle(Candle candle) + { + _influxDbRepository.Write(write => + { + PriceDto price = PriceHelpers.Map(candle); + write.WriteMeasurement( + price, + InfluxDB.Client.Api.Domain.WritePrecision.Ns, + _priceBucket, + _influxDbRepository.Organization); + }); + } + + public void Test(Candle candle) + { + _influxDbRepository.Write(write => + { + var point = PointData.Measurement(""); + PriceDto price = PriceHelpers.Map(candle); + point.Tag("", ""); + point.Timestamp(price.OpenTime, InfluxDB.Client.Api.Domain.WritePrecision.Ns); + write.WritePoint( + point, + _priceBucket, + _influxDbRepository.Organization); + }); + } +} diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbRepository.cs b/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbRepository.cs new file mode 100644 index 0000000..b9c6940 --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbRepository.cs @@ -0,0 +1,11 @@ +using InfluxDB.Client; + +namespace Managing.Infrastructure.Databases; + +public interface IInfluxDbRepository +{ + string Organization { get; } + + Task QueryAsync(Func> action); + void Write(Action action); +} diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbSettings.cs b/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbSettings.cs new file mode 100644 index 0000000..86795fa --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/Abstractions/IInfluxDbSettings.cs @@ -0,0 +1,8 @@ +namespace Managing.Infrastructure.Databases.Abstractions; + +public interface IInfluxDbSettings +{ + string Url { get; set; } + string Token { get; set; } + string Organization { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/InfluxDb/InfluxDbRepository.cs b/src/Managing.Infrastructure.Database/InfluxDb/InfluxDbRepository.cs new file mode 100644 index 0000000..564a419 --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/InfluxDbRepository.cs @@ -0,0 +1,32 @@ +using InfluxDB.Client; +using Managing.Infrastructure.Databases.Abstractions; + +namespace Managing.Infrastructure.Databases.InfluxDb; + +public class InfluxDbRepository : IInfluxDbRepository +{ + private readonly string _token; + private readonly string _url; + public string Organization { get; set; } + + public InfluxDbRepository(IInfluxDbSettings settings) + { + _token = settings.Token; + _url = settings.Url; + Organization = settings.Organization; + } + + public void Write(Action action) + { + using var client = InfluxDBClientFactory.Create(_url, _token); + using var write = client.GetWriteApi(); + action(write); + } + + public async Task QueryAsync(Func> action) + { + using var client = InfluxDBClientFactory.Create(_url, _token); + var query = client.GetQueryApi(); + return await action(query); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Models/InfluxDbSettings.cs b/src/Managing.Infrastructure.Database/InfluxDb/Models/InfluxDbSettings.cs new file mode 100644 index 0000000..22dae14 --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/Models/InfluxDbSettings.cs @@ -0,0 +1,10 @@ +using Managing.Infrastructure.Databases.Abstractions; + +namespace Managing.Infrastructure.Databases.InfluxDb.Models; + +public class InfluxDbSettings : IInfluxDbSettings +{ + public string Url { get ; set; } + public string Token { get; set; } + public string Organization { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Models/PriceDto.cs b/src/Managing.Infrastructure.Database/InfluxDb/Models/PriceDto.cs new file mode 100644 index 0000000..f876d5d --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/Models/PriceDto.cs @@ -0,0 +1,36 @@ +using InfluxDB.Client.Core; + +namespace Managing.Infrastructure.Databases.InfluxDb.Models; + +[Measurement("price")] +public class PriceDto +{ + [Column("exchange", IsTag = true)] + public string Exchange { get; set; } + [Column("ticker", IsTag = true)] + public string Ticker { get; set; } + [Column("timeframe", IsTag = true)] + public string Timeframe { get; set; } + [Column("openTime", IsTimestamp = true)] + public DateTime OpenTime { get; set; } + [Column("closeTime", IsTimestamp = true)] + public DateTime CloseTime { get; set; } + [Column("open")] + public decimal Open { get; set; } + [Column("close")] + public decimal Close { get; set; } + [Column("high")] + public decimal High { get; set; } + [Column("low")] + public decimal Low { get; set; } + [Column("baseVolume")] + public decimal BaseVolume { get; set; } + [Column("quoteVolume")] + public decimal QuoteVolume { get; set; } + [Column("TradeCount")] + public int TradeCount { get; set; } + [Column("takerBuyBaseVolume")] + public decimal TakerBuyBaseVolume { get; set; } + [Column("takerBuyQuoteVolume")] + public decimal TakerBuyQuoteVolume { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Models/TickerDto.cs b/src/Managing.Infrastructure.Database/InfluxDb/Models/TickerDto.cs new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/Models/TickerDto.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs b/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs new file mode 100644 index 0000000..1cb2cbe --- /dev/null +++ b/src/Managing.Infrastructure.Database/InfluxDb/PriceHelpers.cs @@ -0,0 +1,58 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Infrastructure.Databases.InfluxDb.Models; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.InfluxDb; + +public static class PriceHelpers +{ + public static PriceDto Map(Candle candle) + { + var price = new PriceDto + { + Exchange = candle.Exchange.ToString(), + Ticker = candle.Ticker, + OpenTime = candle.OpenTime.ToUniversalTime(), + Open = candle.Open, + Close = candle.Close, + CloseTime = candle.Date.ToUniversalTime(), + High = candle.High, + Low = candle.Low, + BaseVolume = candle.BaseVolume, + QuoteVolume = candle.QuoteVolume, + TradeCount = candle.TradeCount, + TakerBuyBaseVolume = candle.TakerBuyBaseVolume, + TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, + Timeframe = candle.Timeframe.ToString() + }; + + return price; + } + + public static Candle Map(PriceDto dto) + { + return new Candle + { + Exchange = MiscExtensions.ParseEnum(dto.Exchange), + Ticker = dto.Ticker, + OpenTime = dto.OpenTime, + Open = dto.Open, + Close = dto.Close, + Date = dto.CloseTime, + High = dto.High, + Low = dto.Low, + BaseVolume = dto.BaseVolume, + QuoteVolume = dto.QuoteVolume, + TradeCount = dto.TradeCount, + TakerBuyBaseVolume = dto.TakerBuyBaseVolume, + TakerBuyQuoteVolume = dto.TakerBuyQuoteVolume, + Timeframe = MiscExtensions.ParseEnum(dto.Timeframe) + }; + } + + internal static IEnumerable Map(IEnumerable candles) + { + return candles.Select(candle => Map(candle)); + } +} diff --git a/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj b/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj new file mode 100644 index 0000000..1588232 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs b/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs new file mode 100644 index 0000000..db50b0b --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Abstractions/IMongoRepository.cs @@ -0,0 +1,57 @@ +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using MongoDB.Driver; +using System.Linq.Expressions; + +namespace Managing.Infrastructure.Databases.MongoDb.Abstractions +{ + public interface IMongoRepository where TDocument : IDocument + { + IQueryable AsQueryable(); + + IEnumerable FilterBy( + Expression> filterExpression); + + IEnumerable FindAll(); + IEnumerable FilterBy(FilterDefinition filter); + + IEnumerable FilterBy( + Expression> filterExpression, + Expression> projectionExpression); + + TDocument FindOne(Expression> filterExpression); + + Task FindOneAsync(Expression> filterExpression); + + TDocument FindById(string id); + + Task FindByIdAsync(string id); + + void InsertOne(TDocument document); + + Task InsertOneAsync(TDocument document); + + void InsertMany(ICollection documents); + + Task InsertManyAsync(ICollection documents); + + void ReplaceOne(TDocument document); + + Task ReplaceOneAsync(TDocument document); + + void DeleteOne(Expression> filterExpression); + + Task DeleteOneAsync(Expression> filterExpression); + + void DeleteById(string id); + + Task DeleteByIdAsync(string id); + + void DeleteMany(Expression> filterExpression); + + Task DeleteManyAsync(Expression> filterExpression); + + void Update(TDocument entity); + void CreateIndex(string column); + void DropCollection(); + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs b/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs new file mode 100644 index 0000000..801575c --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Attributes/BsonCollectionAttribute.cs @@ -0,0 +1,13 @@ +namespace Managing.Infrastructure.Databases.MongoDb.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class BsonCollectionAttribute : Attribute + { + public string CollectionName { get; } + + public BsonCollectionAttribute(string collectionName) + { + CollectionName = collectionName; + } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs new file mode 100644 index 0000000..3a99df6 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/AccountDto.cs @@ -0,0 +1,16 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Accounts")] +public class AccountDto : Document +{ + public UserDto User { get; set; } + public string Name { get; set; } + public TradingExchanges Exchanges { get; set; } + public AccountType Type { get; set; } + public string Key { get; set; } + public string Secret { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs new file mode 100644 index 0000000..73dc12f --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BacktestDto.cs @@ -0,0 +1,26 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.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 Ticker Ticker { get; set; } + public string Scenario { get; set; } + public List Positions { get; set; } + public List Signals { get; set; } + public Timeframe Timeframe { get; set; } + public RiskLevel RiskLevel { get; set; } + public string AccountName { get; set; } + public List Candles { get; set; } + public BotType BotType { get; set; } + public MoneyManagementDto MoneyManagement { get; internal set; } + public MoneyManagementDto OptimizedMoneyManagement { get; internal set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs new file mode 100644 index 0000000..db2284f --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BadTraderDto.cs @@ -0,0 +1,17 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + + +[BsonCollection("BadTrader")] +public class BadTraderDto : Document +{ + public string Address { get; set; } + public int Winrate { get; set; } + public decimal Pnl { get; set; } + public int TradeCount { get; set; } + public decimal AverageWin { get; set; } + public decimal AverageLoss { get; set; } + public decimal Roi { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs new file mode 100644 index 0000000..52940f9 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BestTraderDto.cs @@ -0,0 +1,16 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("BestTrader")] +public class BestTraderDto : Document +{ + public string Address { get; set; } + public int Winrate { get; set; } + public decimal Pnl { get; set; } + public int TradeCount { get; set; } + public decimal AverageWin { get; set; } + public decimal AverageLoss { get; set; } + public decimal Roi { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs new file mode 100644 index 0000000..1341fb2 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/CandleDto.cs @@ -0,0 +1,28 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using MongoDB.Bson.Serialization.Attributes; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections +{ + [BsonCollection("Candles")] + public class CandleDto : Document + { + public TradingExchanges 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; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs new file mode 100644 index 0000000..ea2a718 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/FeeDto.cs @@ -0,0 +1,13 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Fees")] +public class FeeDto : Document +{ + public decimal Cost { get; set; } + public TradingExchanges Exchange { get; set; } + public DateTime LastUpdate { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs new file mode 100644 index 0000000..7a840d7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/MoneyManagementDto.cs @@ -0,0 +1,18 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.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 Leverage { get; set; } + public string Name { get; internal set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs new file mode 100644 index 0000000..63d4890 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/PositionDto.cs @@ -0,0 +1,27 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using MongoDB.Bson.Serialization.Attributes; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.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 PositionStatus Status { get; set; } + public Ticker Ticker { get; set; } + public string SignalIdentifier { get; set; } + public string AccountName { get; set; } + public MoneyManagementDto MoneyManagement { get; set; } + public PositionInitiator Initiator { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs new file mode 100644 index 0000000..efafcd8 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/ScenarioDto.cs @@ -0,0 +1,12 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections +{ + [BsonCollection("Scenarios")] + public class ScenarioDto : Document + { + public string Name { get; set; } + public List Strategies { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs new file mode 100644 index 0000000..644b0eb --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/SignalDto.cs @@ -0,0 +1,21 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.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 Ticker Ticker { get; set; } + public SignalStatus Status { get; set; } + public Timeframe Timeframe { get; set; } + public StrategyType Type { get; set; } + public SignalType SignalType { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs new file mode 100644 index 0000000..9d4551d --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/SpotlighOverviewDto.cs @@ -0,0 +1,30 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("SpotlighOverview")] +public class SpotlighOverviewDto : Document +{ + public List Spotlights { get; set; } + public DateTime DateTime { get; set; } + public Guid Identifier { get; set; } + public int ScenarioCount { get; set; } +} + +public class SpotlightDto +{ + public ScenarioDto Scenario { get; set; } + public List TickerSignals { get; set; } +} + +public class TickerSignalDto +{ + public Ticker Ticker { get; set; } + public List FiveMinutes { get; set; } + public List FifteenMinutes { get; set; } + public List OneHour { get; set; } + public List FourHour { get; set; } + public List OneDay { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/StrategyDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/StrategyDto.cs new file mode 100644 index 0000000..87d2efd --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/StrategyDto.cs @@ -0,0 +1,23 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections +{ + [BsonCollection("Strategies")] + public class StrategyDto : Document + { + public StrategyType Type { get; set; } + public Timeframe Timeframe { 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; } + public int? CyclePeriods { get; set; } + public SignalType SignalType { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs new file mode 100644 index 0000000..a805032 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/TopVolumeTickerDto.cs @@ -0,0 +1,15 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.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 TradingExchanges Exchange { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs new file mode 100644 index 0000000..6829121 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/TradeDto.cs @@ -0,0 +1,24 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using MongoDB.Bson.Serialization.Attributes; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections +{ + [BsonCollection("Trades")] + public class TradeDto : Document + { + [BsonDateTimeOptions] + public DateTime Date { get; set; } + public TradeDirection Direction { get; set; } + public TradeStatus Status { get; set; } + public TradeType TradeType { get; set; } + public Ticker Ticker { get; set; } + public decimal Fee { get; set; } + public decimal Quantity { get; set; } + public decimal Price { get; set; } + public decimal Leverage { get; set; } + public string ExchangeOrderId { get; set; } + public string Message { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs new file mode 100644 index 0000000..4f94b3b --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs @@ -0,0 +1,10 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Users")] +public class UserDto : Document +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs new file mode 100644 index 0000000..a9e1de2 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkerDto.cs @@ -0,0 +1,16 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Workers")] +public class WorkerDto : Document +{ + public WorkerType WorkerType { get; set; } + public DateTime StartTime { get; set; } + public DateTime? LastRunTime { get; set; } + public int ExecutionCount { get; set; } + public TimeSpan Delay { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs new file mode 100644 index 0000000..dcc363f --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/WorkflowDto.cs @@ -0,0 +1,16 @@ +using Managing.Domain.Workflows.Synthetics; +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Workflow")] +public class WorkflowDto : Document +{ + public string Name { get; set; } + public string Description { get; set; } + public WorkflowUsage Usage { get; set; } + public List Flows { get; set; } +} + diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs new file mode 100644 index 0000000..6d3cef5 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Configurations/Document.cs @@ -0,0 +1,13 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Managing.Infrastructure.Databases.MongoDb.Configurations +{ + public abstract class Document : IDocument + { + [BsonId] + public ObjectId Id { get; set; } + [BsonDateTimeOptions] + public DateTime CreatedAt => Id.CreationTime; + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs new file mode 100644 index 0000000..46663df --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IDocument.cs @@ -0,0 +1,14 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Managing.Infrastructure.Databases.MongoDb.Configurations +{ + public interface IDocument + { + [BsonId] + [BsonRepresentation(BsonType.String)] + ObjectId Id { get; set; } + + DateTime CreatedAt { get; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs new file mode 100644 index 0000000..9b778cb --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Configurations/IManagingDatabaseSettings.cs @@ -0,0 +1,8 @@ +namespace Managing.Infrastructure.Databases.MongoDb +{ + public interface IManagingDatabaseSettings + { + string ConnectionString { get; set; } + string DatabaseName { get; set; } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs b/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs new file mode 100644 index 0000000..ce2f7af --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Configurations/ManagingDatabaseSettings.cs @@ -0,0 +1,7 @@ +namespace Managing.Infrastructure.Databases.MongoDb; + +public class ManagingDatabaseSettings : IManagingDatabaseSettings +{ + public string ConnectionString { get; set; } + public string DatabaseName { get; set; } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs new file mode 100644 index 0000000..b0cb9d5 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoHelpers.cs @@ -0,0 +1,20 @@ +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Managing.Infrastructure.Databases.MongoDb +{ + public static class MongoHelpers + { + public static async Task EnsureIndexExists(this IMongoDatabase database, string collectionName, string indexName) + { + var collection = database.GetCollection(collectionName); + var index = new BsonDocument + { + {indexName, 1} + }; + + var indexModel = new CreateIndexModel(index, new CreateIndexOptions { Unique = true }); + await collection.Indexes.CreateOneAsync(indexModel).ConfigureAwait(false); + } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs new file mode 100644 index 0000000..8412872 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -0,0 +1,654 @@ +using Managing.Domain.Accounts; +using Managing.Domain.Backtests; +using Managing.Domain.Candles; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; +using Managing.Domain.Statistics; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Domain.Users; +using Managing.Domain.Workers; +using Managing.Domain.Workflows.Synthetics; +using Managing.Infrastructure.Databases.MongoDb.Collections; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb; + +public static class MongoMappers +{ + #region Statistics + internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker) + { + return new TopVolumeTickerDto + { + Ticker = topVolumeTicker.Ticker, + Date = topVolumeTicker.Date, + Volume = topVolumeTicker.Volume, + Rank = topVolumeTicker.Rank, + Exchange = topVolumeTicker.Exchange + }; + } + + internal static IList Map(IEnumerable top) + { + return top.Select(topElement => new TopVolumeTicker + { + Ticker = topElement.Ticker, + Date = topElement.Date, + Volume = topElement.Volume, + Rank = topElement.Rank, + Exchange = topElement.Exchange + }).ToList(); + } + + #endregion + + #region Accounts + internal static AccountDto Map(Account request) + { + return new AccountDto + { + Name = request.Name, + Exchanges = request.Exchange, + Key = request.Key, + Secret = request.Secret, + Type = request.Type, + User = Map(request.User) + }; + } + + internal static IEnumerable Map(IEnumerable accounts) + { + return accounts.Select(account => Map(account)); + } + + internal static Account Map(AccountDto account, bool hideKeys = false) + { + if (account == null) return null; + + var a = new Account + { + Name = account.Name, + Exchange = account.Exchanges, + }; + + if (!hideKeys) + { + a.Key = account.Key; + a.Secret = account.Secret; + } + + a.Exchange = account.Exchanges; + a.Type = account.Type; + a.User = Map(account.User); + + return a; + } + #endregion + + #region Workers + internal static WorkerDto Map(Worker worker) + { + return new WorkerDto + { + WorkerType = worker.WorkerType, + StartTime = worker.StartTime, + LastRunTime = worker.LastRunTime, + ExecutionCount = worker.ExecutionCount, + Delay = worker.Delay + }; + } + + internal static Worker Map(WorkerDto worker) + { + if (worker == null) + return null; + + return new Worker + { + WorkerType = worker.WorkerType, + StartTime = worker.StartTime, + LastRunTime = worker.LastRunTime, + ExecutionCount = worker.ExecutionCount, + Delay = worker.Delay, + IsActive = worker.IsActive + }; + } + + #endregion + + #region Backtests + 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) + { + Id = b.Id.ToString(), + FinalPnl = b.FinalPnl, + WinRate = b.WinRate, + GrowthPercentage = b.GrowthPercentage, + HodlPercentage = b.HodlPercentage, + MoneyManagement = Map(b.MoneyManagement), + OptimizedMoneyManagement = Map(b.OptimizedMoneyManagement) + }; + } + + internal static BacktestDto Map(Backtest result) + { + var backtest = new BacktestDto + { + FinalPnl = result.FinalPnl, + WinRate = result.WinRate, + GrowthPercentage = result.GrowthPercentage, + HodlPercentage = result.HodlPercentage, + Candles = Map(result.Candles), + Positions = Map(result.Positions), + AccountName = result.AccountName, + BotType = result.BotType, + MoneyManagement = Map(result.MoneyManagement), + OptimizedMoneyManagement = Map(result.OptimizedMoneyManagement), + }; + + backtest.Timeframe = result.Timeframe; + backtest.Ticker = result.Ticker; + backtest.Scenario = result.Scenario; + + return backtest; + } + + #endregion + + #region Candles + public static Candle Map(CandleDto candle) + { + if (candle == null) + return null; + + return new Candle() + { + Ticker = candle.Ticker, + BaseVolume = candle.BaseVolume, + Close = candle.Close, + Date = candle.OpenTime, + Open = candle.Open, + OpenTime = candle.OpenTime, + High = candle.High, + Low = candle.Low, + QuoteVolume = candle.QuoteVolume, + TakerBuyBaseVolume = candle.TakerBuyBaseVolume, + TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, + TradeCount = candle.TradeCount, + Exchange = candle.Exchange, + }; + } + + public static CandleDto Map(Candle candle) + { + return new CandleDto + { + Exchange = candle.Exchange, + Ticker = candle.Ticker, + OpenTime = candle.OpenTime, + Open = candle.Open, + Close = candle.Close, + High = candle.High, + Low = candle.Low, + BaseVolume = candle.BaseVolume, + QuoteVolume = candle.QuoteVolume, + TradeCount = candle.TradeCount, + TakerBuyBaseVolume = candle.TakerBuyBaseVolume, + TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume + }; + } + + public static List Map(List candles) + { + return candles.ConvertAll(candle => Map(candle)); + } + + + #endregion + + #region Positions + public static PositionDto Map(Position position) + { + var p = new PositionDto + { + Date = position.Date, + Open = Map(position.Open), + OriginDirection = position.OriginDirection, + Identifier = position.Identifier, + SignalIdentifier = position.SignalIdentifier, + Status = position.Status, + AccountName = position.AccountName, + MoneyManagement = Map(position.MoneyManagement), + Initiator = position.Initiator, + Ticker = position.Ticker + }; + + if (position.StopLoss != null) + p.StopLoss = Map(position.StopLoss); + + if (position.TakeProfit1 != null) + p.TakeProfit1 = Map(position.TakeProfit1); + + if (position.TakeProfit2 != null) + p.TakeProfit2 = Map(position.TakeProfit2); + + if (position.ProfitAndLoss != null) + p.ProfitAndLoss = position.ProfitAndLoss.Realized; + + return p; + } + + private static TradeDto Map(Trade trade) + { + return new TradeDto + { + Date = trade.Date, + Direction = trade.Direction, + Status = trade.Status, + TradeType = trade.TradeType, + Ticker = trade.Ticker, + Quantity = trade.Quantity, + Price = trade.Price, + Leverage = trade.Leverage, + ExchangeOrderId = trade.ExchangeOrderId, + Message = trade.Message + }; + } + + public static Position Map(PositionDto dto) + { + var position = new Position(dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker, Map(dto.MoneyManagement), dto.Initiator, dto.Date) + { + Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status, tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity, price: dto.Open.Price, leverage: dto.Open.Leverage, exchangeOrderId: dto.Open.ExchangeOrderId, message: dto.Open.Message), + ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss }, + Status = dto.Status, + SignalIdentifier = dto.SignalIdentifier, + Identifier = dto.Identifier + }; + + if (dto.StopLoss != null) + { + position.StopLoss = new Trade(date: dto.StopLoss.Date, direction: dto.StopLoss.Direction, status: dto.StopLoss.Status, tradeType: dto.StopLoss.TradeType, ticker: dto.StopLoss.Ticker, quantity: dto.StopLoss.Quantity, price: dto.StopLoss.Price, leverage: dto.StopLoss.Leverage, exchangeOrderId: dto.StopLoss.ExchangeOrderId, message: dto.StopLoss.Message); + } + + if (dto.TakeProfit1 != null) + { + position.TakeProfit1 = new Trade(date: dto.TakeProfit1.Date, direction: dto.TakeProfit1.Direction, status: dto.TakeProfit1.Status, tradeType: dto.TakeProfit1.TradeType, ticker: dto.TakeProfit1.Ticker, quantity: dto.TakeProfit1.Quantity, price: dto.TakeProfit1.Price, leverage: dto.TakeProfit1.Leverage, exchangeOrderId: dto.TakeProfit1.ExchangeOrderId, message: dto.TakeProfit1.Message); + } + + if (dto.TakeProfit2 != null) + { + position.TakeProfit2 = new Trade(date: dto.TakeProfit2.Date, direction: dto.TakeProfit2.Direction, status: dto.TakeProfit2.Status, tradeType: dto.TakeProfit2.TradeType, ticker: dto.TakeProfit2.Ticker, quantity: dto.TakeProfit2.Quantity, price: dto.TakeProfit2.Price, leverage: dto.TakeProfit2.Leverage, exchangeOrderId: dto.TakeProfit2.ExchangeOrderId, message: dto.TakeProfit2.Message); + } + + return position; + } + + internal static List Map(List positions) + { + return positions.ConvertAll(position => Map(position)); + } + + #endregion + + #region Signals + public static SignalDto Map(Signal signal) + { + return new SignalDto + { + Identifier = signal.Identifier, + Direction = signal.Direction, + Candle = Map(signal.Candle), + Confidence = signal.Confidence, + Date = signal.Date, + Ticker = signal.Ticker, + Status = signal.Status, + Timeframe = signal.Timeframe, + Type = signal.StrategyType + }; + } + + 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, timeframe: bSignal.Timeframe, + strategyType: bSignal.Type, signalType: bSignal.SignalType) + { + Status = bSignal.Status + }; + } + + #endregion + + #region Scenarios + public static ScenarioDto Map(Scenario scenario) + { + return new ScenarioDto() + { + Name = scenario.Name, + Strategies = Map(scenario.Strategies), + }; + } + + internal static IEnumerable Map(IEnumerable dtos) + { + return dtos.Select(d => Map(d)); + } + + internal static Scenario Map(ScenarioDto d) + { + return new Scenario(d.Name) + { + Name = d.Name, + Strategies = Map(d.Strategies).ToList() + }; + } + + private static List Map(List strategies) + { + return strategies.ConvertAll(strategy => Map(strategy)); + } + + internal static Strategy Map(StrategyDto strategyDto) + { + return new Strategy(name: strategyDto.Name, timeframe: strategyDto.Timeframe, type: strategyDto.Type) + { + Period = strategyDto.Period, + FastPeriods = strategyDto.FastPeriods, + SlowPeriods = strategyDto.SlowPeriods, + SignalPeriods = strategyDto.SignalPeriods, + Multiplier = strategyDto.Multiplier, + SmoothPeriods = strategyDto.SmoothPeriods, + StochPeriods = strategyDto.StochPeriods, + CyclePeriods = strategyDto.CyclePeriods + }; + } + internal static StrategyDto Map(Strategy strategy) + { + var dto = new StrategyDto + { + Type = strategy.Type, + Timeframe = strategy.Timeframe, + Name = strategy.Name, + SignalType = strategy.SignalType + }; + + 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 Map(IEnumerable strategies) + { + return strategies.Select(strategy => Map(strategy)); + } + + #endregion + + #region Money Management + public static MoneyManagementDto Map(MoneyManagement request) + { + if (request == null) return null; + return new MoneyManagementDto + { + Timeframe = request.Timeframe, + BalanceAtRisk = request.BalanceAtRisk, + StopLoss = request.StopLoss, + TakeProfit = request.TakeProfit, + Leverage = request.Leverage, + Name = request.Name + }; + } + + public static MoneyManagement Map(MoneyManagementDto request) + { + if (request == null) + return null; + + return new MoneyManagement + { + Timeframe = request.Timeframe, + BalanceAtRisk = request.BalanceAtRisk, + StopLoss = request.StopLoss, + TakeProfit = request.TakeProfit, + Leverage = request.Leverage, + Name = request.Name + }; + } + + internal static User Map(UserDto user) + { + if (user == null) + return null; + + return new User + { + Name = user.Name, + }; + } + + internal static UserDto Map(User user) + { + return new UserDto + { + Name = user.Name + }; + } + + internal static SpotlighOverviewDto Map(SpotlightOverview overview) + { + return new SpotlighOverviewDto + { + Spotlights = Map(overview.Spotlights), + DateTime = overview.DateTime, + Identifier = overview.Identifier, + ScenarioCount = overview.ScenarioCount, + }; + } + + private static List Map(List spotlights) + { + return spotlights.ConvertAll(spotlight => new SpotlightDto + { + Scenario = new ScenarioDto + { + Name = spotlight.Scenario.Name, + Strategies = spotlight.Scenario.Strategies.ConvertAll(spotlightScenarioStrategy => Map(spotlightScenarioStrategy)) + }, + TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignalDto + { + Ticker = spotlightTickerSignal.Ticker, + FiveMinutes = spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)) ?? new List(), + FifteenMinutes = spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)) ?? new List(), + OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)) ?? new List(), + FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)) ?? new List(), + OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay)) ?? new List() + }) + }); + } + + internal static SpotlightOverview Map(SpotlighOverviewDto overview) + { + return new SpotlightOverview + { + Spotlights = Map(overview.Spotlights), + DateTime = overview.DateTime, + Identifier = overview.Identifier, + ScenarioCount = overview.ScenarioCount + }; + } + + private static List Map(List spotlights) + { + return spotlights.ConvertAll(spotlight => new Spotlight + { + Scenario = new Scenario(name: spotlight.Scenario.Name) + { + Strategies = spotlight.Scenario.Strategies.ConvertAll(spotlightScenarioStrategy => Map(spotlightScenarioStrategy)) + }, + TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignal + { + Ticker = spotlightTickerSignal.Ticker, + FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)), + FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)), + OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)), + FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)), + OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay)) + }) + }); + } + + internal static IList Map(IEnumerable overviews) + { + return overviews.Select(Map).ToList(); + } + + + internal static FeeDto Map(Fee fee) + { + return new FeeDto + { + Cost = fee.Cost, + Exchange = fee.Exchange, + LastUpdate = fee.LastUpdate + }; + } + + internal static Fee Map(FeeDto fee) + { + if (fee == null) return null; + + return new Fee + { + Cost = fee.Cost, + Exchange = fee.Exchange, + LastUpdate = fee.LastUpdate + }; + } + + internal static List Map(IEnumerable enumerable) + { + return enumerable.Select(enumerableElement => new Trader + { + Address = enumerableElement.Address, + Winrate = enumerableElement.Winrate, + Pnl = enumerableElement.Pnl, + TradeCount = enumerableElement.TradeCount, + AverageWin = enumerableElement.AverageWin, + AverageLoss = enumerableElement.AverageLoss, + Roi = enumerableElement.Roi + }).ToList(); + } + + internal static BestTraderDto Map(Trader trader) + { + return new BestTraderDto + { + Address = trader.Address, + Winrate = trader.Winrate, + Pnl = trader.Pnl, + TradeCount = trader.TradeCount, + AverageWin = trader.AverageWin, + AverageLoss = trader.AverageLoss, + Roi = trader.Roi + }; + } + + internal static List Map(IEnumerable enumerable) + { + return enumerable.Select(enumerableElement => new Trader + { + Address = enumerableElement.Address, + Winrate = enumerableElement.Winrate, + Pnl = enumerableElement.Pnl, + TradeCount = enumerableElement.TradeCount, + AverageWin = enumerableElement.AverageWin, + AverageLoss = enumerableElement.AverageLoss, + Roi = enumerableElement.Roi + }).ToList(); + } + + internal static BadTraderDto BadTraderMap(Trader trader) + { + return new BadTraderDto + { + Address = trader.Address, + Winrate = trader.Winrate, + Pnl = trader.Pnl, + TradeCount = trader.TradeCount, + AverageWin = trader.AverageWin, + AverageLoss = trader.AverageLoss, + Roi = trader.Roi + }; + } + + internal static WorkflowDto Map(SyntheticWorkflow workflow) + { + return new WorkflowDto + { + Name = workflow.Name, + Description = workflow.Description, + Usage = workflow.Usage, + Flows = workflow.Flows + }; + } + + internal static SyntheticWorkflow Map(WorkflowDto m) + { + if (m == null) return null; + + return new SyntheticWorkflow + { + Name = m.Name, + Usage = m.Usage, + Description = m.Description, + Flows = m.Flows.ToList(), + }; + } + + #endregion +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs new file mode 100644 index 0000000..6e36ee7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoRepository.cs @@ -0,0 +1,178 @@ +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using MongoDB.Bson; +using MongoDB.Driver; +using System.Linq.Expressions; + +namespace Managing.Infrastructure.Databases.MongoDb +{ + public class MongoRepository : IMongoRepository + where TDocument : IDocument + { + private readonly IMongoCollection _collection; + private readonly IMongoDatabase _database; + + public MongoRepository(IManagingDatabaseSettings settings) + { + _database = new MongoClient(settings.ConnectionString).GetDatabase(settings.DatabaseName); + _collection = _database.GetCollection(GetCollectionName(typeof(TDocument))); + } + + private protected string GetCollectionName(Type documentType) + { + return ((BsonCollectionAttribute)documentType.GetCustomAttributes( + typeof(BsonCollectionAttribute), + true) + .FirstOrDefault())?.CollectionName; + } + + public virtual IQueryable AsQueryable() + { + return _collection.AsQueryable(); + } + + public virtual IEnumerable FilterBy( + Expression> filterExpression) + { + return _collection.Find(filterExpression).ToEnumerable(); + } + + public virtual IEnumerable FilterBy(FilterDefinition filter) + { + return _collection.Find(filter).ToEnumerable(); + } + + public virtual IEnumerable FindAll() + { + return _collection.Find(_ => true).ToEnumerable(); + } + + public virtual IEnumerable FilterBy( + Expression> filterExpression, + Expression> projectionExpression) + { + return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable(); + } + + public virtual TDocument FindOne(Expression> filterExpression) + { + return _collection.Find(filterExpression).FirstOrDefault(); + } + + public virtual Task FindOneAsync(Expression> filterExpression) + { + return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync()); + } + + public virtual TDocument FindById(string id) + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + return _collection.Find(filter).SingleOrDefault(); + } + + public virtual Task FindByIdAsync(string id) + { + return Task.Run(() => + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + return _collection.Find(filter).SingleOrDefaultAsync(); + }); + } + + public virtual void InsertOne(TDocument document) + { + _collection.InsertOne(document); + } + + public virtual Task InsertOneAsync(TDocument document) + { + return Task.Run(() => _collection.InsertOneAsync(document)); + } + + public void InsertMany(ICollection documents) + { + _collection.InsertMany(documents); + } + + public void DropCollection() + { + _database.DropCollection(GetCollectionName(typeof(TDocument))); + } + + public virtual async Task InsertManyAsync(ICollection documents) + { + await _collection.InsertManyAsync(documents); + } + + public void ReplaceOne(TDocument document) + { + var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); + _collection.FindOneAndReplace(filter, document); + } + + public virtual async Task ReplaceOneAsync(TDocument document) + { + var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); + await _collection.FindOneAndReplaceAsync(filter, document); + } + + public virtual void Update(TDocument entity) + { + if (entity.Id == ObjectId.Empty) + { + entity.Id = ObjectId.GenerateNewId(); + } + + var option = new ReplaceOptions { IsUpsert = true }; + _collection.ReplaceOne(x => entity != null && x.Id == entity.Id, entity, option); + } + + public virtual void DeleteOne(Expression> filterExpression) + { + _collection.FindOneAndDelete(filterExpression); + } + + public virtual Task DeleteOneAsync(Expression> filterExpression) + { + return Task.Run(() => _collection.FindOneAndDeleteAsync(filterExpression)); + } + + public virtual void DeleteById(string id) + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + _collection.FindOneAndDelete(filter); + } + + public virtual Task DeleteByIdAsync(string id) + { + return Task.Run(() => + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + _collection.FindOneAndDeleteAsync(filter); + }); + } + + public virtual void DeleteMany(Expression> filterExpression) + { + _collection.DeleteMany(filterExpression); + } + + public virtual Task DeleteManyAsync(Expression> filterExpression) + { + return _collection.DeleteManyAsync(filterExpression); + } + + public virtual void CreateIndex(string column) + { + var keys = Builders.IndexKeys.Ascending(column); + var indexOptions = new CreateIndexOptions { Unique = true }; + var model = new CreateIndexModel(keys, indexOptions); + _collection.Indexes.CreateOne(model); + } + } +} diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz new file mode 100644 index 0000000000000000000000000000000000000000..cb79d135f484f18ef201601266365467f0447a8f GIT binary patch literal 388 zcmV-~0ek)*iwFP!00000|2oLPz`!1#nZl6tH#m(;#Nz6-5bu0migH;&l z8JIFK1()O(_~aKCGl)Wj42_KqkbxlsQ%GWVYCutbT4o7MpAiF7U}<7rNoGkUk|YxY z1A`$06Ufq{#Pn1sp8?qe`A`o`NAkdomxMgPGETxL;Ys&Q`M8AM9cOdv?7VjEGy++DKn z`n`(fwdF-tP*sC0k-xlx?iC+U#X-s(_=n82*e+|;xpkm<|}*zMs8NG;0DPhk*nODrydu!|XZKzf67^2=cYQec71 z^t{9zm=Gt#vKw$eaz?I{@p!oMpr8B#Bu8o3wB14Yk%NJOA*eXhC9})~#fK9iKI|mP zhYYxUh-`}h)PF1t3=CYze&j;-9~Y|M(EPOy>aT@JPTAm(-Ct&O@s}CgUu-1$>j9F# zwnP232+1ioije&UN(e5QWzP9|X_-a2sBtzG;3c@fD#qqm5TxQew(Iz)=;{`;Db*2!cQ*AqVn00960 LqRb)?Fs`J|El8sL|or_WvOHxxDOBfWM zr(SPjWMB{osVqokU}6BVGILYYiV|~E8JHOu7?}J(EG`BH2H(Ww6z8J+;$jA_fYhSQ z{1gTOx5VNS2)mep2c$PRC%+sfAO#l4OwUWqfeCRkFfcH5!kxq!87|}TaOFWi`8h~V zy79Z^4#G(cxSYhnz`zhxoavHTX2u`@br2f^0|OVbqqvYA#D(e@6o({29WocmAup6# zNppw^+#xI^IiwfKA&pRn%tLm_d}N0(;&KQm23#`Bob&V2GK+Gl;h0{iW9B0{=EONN V97Apt0RjL3|NlCuqA9-u001HCay$S4 literal 0 HcmV?d00001 diff --git a/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz b/src/Managing.Infrastructure.Database/MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..743fc3131688f8785c5fbc838c15c90c615f99d4 GIT binary patch literal 133 zcmV;00DAu)iwFP!00000|76NR3c@fD1<<>ETxMZILfW~3XAmh(GD46}#WtdZxw~ZT z`n`tjd6Z3dP*Z~9Od nNQ1ivm(S7L=)4=p$=ei`DMXvH9Rl _moneyManagementRepository; + + public SettingsRepository(IMongoRepository moneyManagementRepository) + { + _moneyManagementRepository = moneyManagementRepository; + } + + public void DeleteMoneyManagement(string name) + { + var moneyManagement = _moneyManagementRepository.FindOne(m => m.Name == name); + _moneyManagementRepository.DeleteById(moneyManagement.Id.ToString()); + } + + public void DeleteMoneyManagements() + { + _moneyManagementRepository.DropCollection(); + } + + public async Task GetMoneyManagement(string name) + { + var moneyManagement = await _moneyManagementRepository.FindOneAsync(m => m.Name == name); + return MongoMappers.Map(moneyManagement); + } + + public IEnumerable GetMoneyManagements() + { + var moneyManagements = _moneyManagementRepository.FindAll(); + return moneyManagements.Select(m => MongoMappers.Map(m)); + } + + public async Task InsertMoneyManagement(MoneyManagement request) + { + await _moneyManagementRepository.InsertOneAsync(MongoMappers.Map(request)); + } + + public void UpdateMoneyManagement(MoneyManagement moneyManagement) + { + var mm = _moneyManagementRepository.FindOne(m => m.Name == moneyManagement.Name); + var dto = MongoMappers.Map(moneyManagement); + dto.Id = mm.Id; + _moneyManagementRepository.Update(dto); + } +} diff --git a/src/Managing.Infrastructure.Database/StatisticRepository.cs b/src/Managing.Infrastructure.Database/StatisticRepository.cs new file mode 100644 index 0000000..7de54b7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/StatisticRepository.cs @@ -0,0 +1,103 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Statistics; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class StatisticRepository : IStatisticRepository +{ + private readonly IMongoRepository _bestTrader; + private readonly IMongoRepository _badTrader; + private readonly IMongoRepository _topRepository; + private readonly IMongoRepository _spotlightRepository; + + public StatisticRepository( + IMongoRepository topRepository, + IMongoRepository spotlightRepository, + IMongoRepository bestTrader, + IMongoRepository badTrader) + { + _topRepository = topRepository; + _spotlightRepository = spotlightRepository; + _bestTrader = bestTrader; + _badTrader = badTrader; + } + + public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker) + { + await _topRepository.InsertOneAsync(MongoMappers.Map(topVolumeTicker)); + } + + public IList GetTopVolumeTickers(DateTime date) + { + var top = _topRepository.FilterBy(t => date < t.Date); + return MongoMappers.Map(top); + } + + public async Task SaveSpotligthtOverview(SpotlightOverview overview) + { + await _spotlightRepository.InsertOneAsync(MongoMappers.Map(overview)); + } + + public IList GetSpotlightOverviews(DateTime date) + { + var overviews = _spotlightRepository.FilterBy(t => date < t.DateTime); + return MongoMappers.Map(overviews); + } + + public void UpdateSpotlightOverview(SpotlightOverview overview) + { + var s = _spotlightRepository.FindOne(o => o.Identifier == overview.Identifier); + var dto = MongoMappers.Map(overview); + dto.Id = s.Id; + _spotlightRepository.Update(dto); + } + + public List GetBestTraders() + { + return MongoMappers.Map(_bestTrader.FindAll()); + } + + public void UpdateBestTrader(Trader trader) + { + var t = _bestTrader.FindOne(t => t.Address == trader.Address); + var dto = MongoMappers.Map(trader); + dto.Id = t.Id; + _bestTrader.Update(dto); + } + + public async Task InsertBestTrader(Trader trader) + { + await _bestTrader.InsertOneAsync(MongoMappers.Map(trader)); + } + + public async Task RemoveBestTrader(Trader trader) + { + await _bestTrader.DeleteOneAsync(t => t.Address == trader.Address); + } + + public List GetBadTraders() + { + return MongoMappers.Map(_badTrader.FindAll()); + } + + public void UpdateBadTrader(Trader trader) + { + var t = _badTrader.FindOne(t => t.Address == trader.Address); + var dto = MongoMappers.BadTraderMap(trader); + dto.Id = t.Id; + _badTrader.Update(dto); + } + + public async Task InsertBadTrader(Trader trader) + { + await _badTrader.InsertOneAsync(MongoMappers.BadTraderMap(trader)); + } + + public async Task RemoveBadTrader(Trader trader) + { + await _badTrader.DeleteOneAsync(t => t.Address == trader.Address); + } +} diff --git a/src/Managing.Infrastructure.Database/TradingRepository.cs b/src/Managing.Infrastructure.Database/TradingRepository.cs new file mode 100644 index 0000000..71006b4 --- /dev/null +++ b/src/Managing.Infrastructure.Database/TradingRepository.cs @@ -0,0 +1,150 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Common; +using Managing.Domain.Scenarios; +using Managing.Domain.Strategies; +using Managing.Domain.Trades; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; +using MongoDB.Driver; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases; + +public class TradingRepository : ITradingRepository +{ + private readonly IMongoRepository _scenarioRepository; + private readonly IMongoRepository _signalRepository; + private readonly IMongoRepository _positionRepository; + private readonly IMongoRepository _strategyRepository; + private readonly IMongoRepository _feeRepository; + + + public TradingRepository( + IMongoRepository scenarioRepository, + IMongoRepository signalRepository, + IMongoRepository positionRepository, + IMongoRepository strategyRepository, + IMongoRepository feeRepository) + { + _scenarioRepository = scenarioRepository; + _signalRepository = signalRepository; + _positionRepository = positionRepository; + _strategyRepository = strategyRepository; + _feeRepository = feeRepository; + } + + public void DeleteScenario(string name) + { + var scenario = _scenarioRepository.FindOne(s => s.Name == name); + _scenarioRepository.DeleteById(scenario.Id.ToString()); + } + + public void DeleteScenarios() + { + _scenarioRepository.DropCollection(); + } + + public void DeleteStrategies() + { + _strategyRepository.DropCollection(); + } + + public void DeleteStrategy(string name) + { + var strategy = _strategyRepository.FindOne(s => s.Name == name); + _strategyRepository.DeleteById(strategy.Id.ToString()); + } + + public Position GetPositionByIdentifier(string identifier) + { + var position = _positionRepository.FindOne(p => p.Identifier == identifier); + return MongoMappers.Map(position); + } + + public IEnumerable GetPositions(PositionInitiator positionInitiator) + { + var filter = Builders.Filter.Eq(p => p.Initiator, positionInitiator); + var positions = _positionRepository.FilterBy(filter); + return positions.Select(MongoMappers.Map); + } + + public IEnumerable GetPositionsByStatus(Enums.PositionStatus positionStatus) + { + var filter = Builders.Filter.Eq(p => p.Status, positionStatus); + var positions = _positionRepository.FilterBy(filter); + return positions.Select(MongoMappers.Map); + } + + public Scenario GetScenarioByName(string name) + { + var scenario = _scenarioRepository.FindOne(s => s.Name == name); + return MongoMappers.Map(scenario); + } + + public IEnumerable GetScenarios() + { + var scenarios = _scenarioRepository.FindAll(); + return scenarios.Select(s => MongoMappers.Map(s)); + } + + public IEnumerable GetStrategies() + { + var strategies = _strategyRepository.FindAll(); + return strategies.Select(MongoMappers.Map); + } + + public Strategy GetStrategyByName(string name) + { + var strategy = _strategyRepository.FindOne(s => s.Name == name); + return MongoMappers.Map(strategy); + } + + public void InsertPosition(Position position) + { + _positionRepository.InsertOne(MongoMappers.Map(position)); + } + + public void InsertScenario(Scenario scenario) + { + _scenarioRepository.CreateIndex(nameof(Scenario.Name)); + _scenarioRepository.InsertOne(MongoMappers.Map(scenario)); + } + + public void InsertSignal(Signal signal) + { + _signalRepository.InsertOne(MongoMappers.Map(signal)); + } + + public void InsertStrategy(Strategy strategy) + { + _strategyRepository.InsertOne(MongoMappers.Map(strategy)); + } + + public void UpdatePosition(Position position) + { + var p = _positionRepository.FindOne(p => p.Open.ExchangeOrderId == position.Open.ExchangeOrderId); + var dto = MongoMappers.Map(position); + dto.Id = p.Id; + _positionRepository.Update(dto); + } + + public Fee GetFee(TradingExchanges exchange) + { + var fee = _feeRepository.FindOne(f => f.Exchange == exchange); + return MongoMappers.Map(fee); + } + + public void InsertFee(Fee fee) + { + _feeRepository.InsertOne(MongoMappers.Map(fee)); + } + + public void UpdateFee(Fee fee) + { + var f = _feeRepository.FindOne(f => f.Exchange == fee.Exchange); + var dto = MongoMappers.Map(fee); + dto.Id = f.Id; + _feeRepository.Update(dto); + } +} diff --git a/src/Managing.Infrastructure.Database/UserRepository.cs b/src/Managing.Infrastructure.Database/UserRepository.cs new file mode 100644 index 0000000..b74647f --- /dev/null +++ b/src/Managing.Infrastructure.Database/UserRepository.cs @@ -0,0 +1,28 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Users; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class UserRepository : IUserRepository +{ + private readonly IMongoRepository _userRepository; + + public UserRepository(IMongoRepository userRepository) + { + _userRepository = userRepository; + } + + public async Task GetUserByNameAsync(string name) + { + var user = await _userRepository.FindOneAsync(u => u.Name == name); + return MongoMappers.Map(user); + } + + public async Task InsertUserAsync(User user) + { + await _userRepository.InsertOneAsync(MongoMappers.Map(user)); + } +} diff --git a/src/Managing.Infrastructure.Database/WorkerRepository.cs b/src/Managing.Infrastructure.Database/WorkerRepository.cs new file mode 100644 index 0000000..f684ee9 --- /dev/null +++ b/src/Managing.Infrastructure.Database/WorkerRepository.cs @@ -0,0 +1,60 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Common; +using Managing.Domain.Workers; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class WorkerRepository : IWorkerRepository +{ + private readonly IMongoRepository _workerRepository; + public WorkerRepository(IMongoRepository workerRepository) + { + _workerRepository = workerRepository; + } + + public async Task DisableWorker(Enums.WorkerType workerType) + { + var worker = await GetWorker(workerType); + worker.IsActive = false; + _workerRepository.Update(worker); + } + + public async Task EnableWorker(Enums.WorkerType workerType) + { + var worker = await GetWorker(workerType); + worker.IsActive = true; + _workerRepository.Update(worker); + } + + public async Task GetWorkerAsync(Enums.WorkerType workerType) + { + return MongoMappers.Map(await GetWorker(workerType)); + } + + public async Task InsertWorker(Worker worker) + { + await _workerRepository.InsertOneAsync(MongoMappers.Map(worker)); + } + + public async Task UpdateWorker(Enums.WorkerType workerType, int executionCount) + { + var worker = await GetWorker(workerType); + worker.ExecutionCount = executionCount; + worker.LastRunTime = DateTime.UtcNow; + _workerRepository.Update(worker); + } + + public IEnumerable GetWorkers() + { + var workers = _workerRepository.FindAll(); + return workers.Select(MongoMappers.Map); + } + + private async Task GetWorker(Enums.WorkerType workerType) + { + return await _workerRepository.FindOneAsync(w => w.WorkerType == workerType); + } +} diff --git a/src/Managing.Infrastructure.Database/WorkflowRepository.cs b/src/Managing.Infrastructure.Database/WorkflowRepository.cs new file mode 100644 index 0000000..e6faaf4 --- /dev/null +++ b/src/Managing.Infrastructure.Database/WorkflowRepository.cs @@ -0,0 +1,48 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Workflows.Synthetics; +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class WorkflowRepository : IWorkflowRepository +{ + private readonly IMongoRepository _workflowRepository; + + public WorkflowRepository(IMongoRepository workflowRepository) + { + _workflowRepository = workflowRepository; + } + public bool DeleteWorkflow(string name) + { + var workflow = _workflowRepository.FindOne(m => m.Name == name); + _workflowRepository.DeleteById(workflow.Id.ToString()); + return true; + } + + public async Task InsertWorkflow(SyntheticWorkflow workflow) + { + await _workflowRepository.InsertOneAsync(MongoMappers.Map(workflow)); + } + + public async Task UpdateWorkflow(SyntheticWorkflow workflow) + { + var w = await _workflowRepository.FindOneAsync(m => m.Name == workflow.Name); + var dto = MongoMappers.Map(workflow); + dto.Id = w.Id; + _workflowRepository.Update(dto); + } + + public async Task GetWorkflow(string name) + { + var workflow = await _workflowRepository.FindOneAsync(m => m.Name == name); + return MongoMappers.Map(workflow); + } + + public IEnumerable GetWorkflows() + { + var workflows = _workflowRepository.FindAll(); + return workflows.Select(m => MongoMappers.Map(m)); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs b/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs new file mode 100644 index 0000000..1746891 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Abstractions/IExchangeProcessor.cs @@ -0,0 +1,39 @@ +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Abstractions; + +public interface IExchangeProcessor +{ + public Enums.TradingExchanges Exchange(); + void LoadClient(Account account); + Task 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); + Task GetBalance(Account account, bool isForPaperTrading = false); + Task> GetBalances(Account account, bool isForPaperTrading = false); + decimal GetPrice(Account account, Ticker ticker, DateTime date); + Task GetTrade(Account account, string order, Ticker ticker); + Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); + decimal GetVolume(Account account, Ticker ticker); + Task> GetTrades(Account account, Ticker ticker); + Task CancelOrder(Account account, Ticker ticker); + decimal GetFee(Account account, bool isForPaperTrading = false); + Candle GetCandle(Account account, Ticker ticker, DateTime date); + Task GetQuantityInPosition(Account account, Ticker ticker); + Orderbook GetOrderbook(Account account, Ticker ticker); + Task> GetOrders(Account account, Ticker ticker); + Task GetTrade(string reference, string orderId, Ticker ticker); +} diff --git a/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs b/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs new file mode 100644 index 0000000..d7f2b7d --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/CandleHelpers.cs @@ -0,0 +1,82 @@ +using Binance.Net.Interfaces; +using FTX.Net.Objects.Models; +using Kraken.Net.Objects.Models; +using Managing.Domain.Candles; +using Managing.Infrastructure.Databases.MongoDb.Collections; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges +{ + public static class CandleHelpers + { + public static CandleDto Convert(IBinanceKline candle, Ticker ticker, Timeframe timeframe) + { + return new CandleDto() + { + Ticker = ticker.ToString(), + Timeframe = timeframe, + BaseVolume = candle.Volume, + Close = candle.ClosePrice, + CloseTime = candle.CloseTime, + Open = candle.OpenPrice, + OpenTime = candle.OpenTime, + High = candle.HighPrice, + Low = candle.LowPrice, + QuoteVolume = candle.QuoteVolume, + TakerBuyBaseVolume = candle.TakerBuyBaseVolume, + TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, + TradeCount = candle.TradeCount, + }; + } + + public static CandleDto Convert(KrakenStreamKline candle, Ticker ticker, Timeframe timeframe) + { + return new CandleDto() + { + Ticker = ticker.ToString(), + Timeframe = timeframe, + BaseVolume = candle.Volume, + Close = candle.ClosePrice, + CloseTime = candle.CloseTime, + Open = candle.OpenPrice, + OpenTime = candle.OpenTime, + High = candle.HighPrice, + Low = candle.LowPrice, + QuoteVolume = candle.VolumeWeightedAveragePrice, + TradeCount = candle.TradeCount + }; + } + + internal static Candle Map(FTXKline lastCandle) + { + return new Candle + { + OpenTime = lastCandle.OpenTime, + High = lastCandle.HighPrice, + Low = lastCandle.LowPrice, + Close = lastCandle.ClosePrice, + Open = lastCandle.OpenPrice + }; + } + + internal static Candle Map(IBinanceStreamKline candle, Ticker ticker, Timeframe timeframe) + { + return new Candle() + { + Ticker = ticker.ToString(), + Timeframe = timeframe, + BaseVolume = candle.Volume, + Close = candle.ClosePrice, + Date = candle.CloseTime, + Open = candle.OpenPrice, + OpenTime = candle.OpenTime, + High = candle.HighPrice, + Low = candle.LowPrice, + QuoteVolume = candle.QuoteVolume, + TakerBuyBaseVolume = candle.TakerBuyBaseVolume, + TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume, + TradeCount = candle.TradeCount, + }; + } + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Configurations/ExchangeConfiguration.cs b/src/Managing.Infrastructure.Exchanges/Configurations/ExchangeConfiguration.cs new file mode 100644 index 0000000..880c5a3 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Configurations/ExchangeConfiguration.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Authentication; +using Microsoft.Extensions.Configuration; + +namespace Managing.Infrastructure.Exchanges.Configurations +{ + public static class ExchangeConfiguration + { + public static ApiCredentials GetBinanceApiCredentials(this IConfiguration configuration) + { + return new ApiCredentials(configuration.GetValue("Credentials:Binance:Key"), configuration.GetValue("Credentials:Binance:Secret")); + } + + public static ApiCredentials GetKrakenApiCredentials(this IConfiguration configuration) + { + return new ApiCredentials(configuration.GetValue("Credentials:Kraken:Key"), configuration.GetValue("Credentials:Kraken:Secret")); + } + + public static ApiCredentials GetFtxApiCredentials(this IConfiguration configuration) + { + return new ApiCredentials(configuration.GetValue("Credentials:Ftx:Key"), configuration.GetValue("Credentials:Ftx:Secret")); + } + } +} diff --git a/src/Managing.Infrastructure.Exchanges/ExchangeService.cs b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs new file mode 100644 index 0000000..d33e1c6 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs @@ -0,0 +1,242 @@ +using Managing.Domain.Trades; +using Managing.Domain.Candles; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; +using Managing.Domain.Accounts; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Infrastructure.Exchanges.Abstractions; + +namespace Managing.Infrastructure.Exchanges +{ + public class ExchangeService : IExchangeService + { + private readonly ILogger _logger; + private readonly ICandleRepository _candleRepository; + private readonly IEnumerable _exchangeProcessor; + + public ExchangeService(ILogger logger, ICandleRepository candleRepository, IEnumerable processor) + { + _logger = logger; + _candleRepository = candleRepository; + _exchangeProcessor = processor; + } + + #region Trades + public async Task 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) + { + _logger.LogInformation($"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage}"); + + if (isForPaperTrading) + { + return BuildEmptyTrade(ticker, price, quantity, direction, leverage, tradeType, currentDate.Value, reduceOnly ? TradeStatus.PendingOpen : TradeStatus.Filled); + } + + var processor = GetProcessor(account); + return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType, reduceOnly, isForPaperTrading, currentDate, ioc); + } + + private IExchangeProcessor GetProcessor(Account account) + { + return _exchangeProcessor.First(e => e.Exchange() == account.Exchange); + } + + public Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, TradeType tradeType, + DateTime dateTime, TradeStatus tradeStatus) + { + return new Trade(dateTime, direction, tradeStatus, tradeType, ticker, quantity, price, leverage, + Guid.NewGuid().ToString(), "EmptyTrade"); + } + + public async Task OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice, + decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null) + { + return await OpenTrade( + account, + ticker, + originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long, + stopLossPrice, + quantity, + tradeType: TradeType.StopLoss, + isForPaperTrading: isForPaperTrading, + currentDate: currentDate, + reduceOnly: true); + } + + public async Task OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice, + decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null) + { + return await OpenTrade( + account, + ticker, + originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long, + takeProfitPrice, + quantity, + tradeType: TradeType.TakeProfit, + isForPaperTrading: isForPaperTrading, + currentDate: currentDate, + reduceOnly: true); + } + + public Task ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false) + { + var direction = position.OriginDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long; + + if (isForPaperTrading) + { + var fake = BuildEmptyTrade(position.Open.Ticker, + lastPrice, + position.Open.Quantity, + direction, + position.Open.Leverage, + TradeType.Market, + position.Open.Date, + TradeStatus.Filled); + return Task.FromResult(fake); + } + + var processor = GetProcessor(account); + var closedTrade = processor.OpenTrade( + account, + position.Ticker, + direction, + lastPrice, + position.Open.Quantity, + position.Open.Leverage, + tradeType: TradeType.Market, + reduceOnly: true).Result; + + if (account.Exchange != Common.Enums.TradingExchanges.Evm) + { + closedTrade.Price = processor.GetTrade(account, closedTrade.ExchangeOrderId, closedTrade.Ticker).Result.Price; + } + + return Task.FromResult(closedTrade); + } + #endregion + + + public async Task CancelOrder(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return await processor.CancelOrder(account, ticker); + } + + public async Task GetTrade(Account account, string orderId, Ticker ticker) + { + var processor = GetProcessor(account); + return await processor.GetTrade(account, orderId, ticker); + } + + public async Task GetTrade(string reference, string orderId, Ticker ticker) + { + var processor = _exchangeProcessor.First(e => e.Exchange() == Common.Enums.TradingExchanges.Evm); + return await processor.GetTrade(reference, orderId, ticker); + } + + public async Task> GetTrades(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return await processor.GetTrades(account, ticker); + + } + + public async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var processor = GetProcessor(account); + return await processor.GetCandles(account, ticker, startDate, timeframe); + } + + public async Task> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var candlesFromRepo = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate); + return candlesFromRepo.ToList(); + } + + public async Task GetBalance(Account account, bool isForPaperTrading = false) + { + if (isForPaperTrading) + { + return 1000; + } + + var processor = GetProcessor(account); + return await processor.GetBalance(account); + } + + public decimal GetFee(Account account, bool isForPaperTrading = false) + { + var processor = GetProcessor(account); + return processor.GetFee(account); + } + + public decimal GetPrice(Account account, Ticker ticker, DateTime date) + { + var processor = GetProcessor(account); + return processor.GetPrice(account, ticker, date); + } + + public Candle GetCandle(Account account, Ticker ticker, DateTime date) + { + var processor = GetProcessor(account); + return processor.GetCandle(account, ticker, date); + } + + public async Task GetQuantityInPosition(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return await processor.GetQuantityInPosition(account, ticker); + } + + public decimal GetVolume(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return processor.GetVolume(account, ticker); + } + + public async Task> GetTickers(Account account, Timeframe timeframe) + { + var tickers = await _candleRepository.GetTickersAsync(account.Exchange, timeframe, DateTime.UtcNow.AddDays(-2)); + return tickers.ToList(); + } + + public decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction) + { + if (account.Exchange == Common.Enums.TradingExchanges.Evm) + { + return GetPrice(account, ticker, DateTime.UtcNow); + } + + return GetOrderbook(account, ticker).GetBestPrice(direction, quantity); + } + + public Orderbook GetOrderbook(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return processor.GetOrderbook(account, ticker); + } + + public async Task> GetBalances(Account account, bool isForPaperTrading = false) + { + var processor = GetProcessor(account); + return await processor.GetBalances(account, isForPaperTrading); + } + + public async Task> GetOpenOrders(Account account, Ticker ticker) + { + var processor = GetProcessor(account); + return await processor.GetOrders(account, ticker); + } + } +} diff --git a/src/Managing.Infrastructure.Exchanges/ExchangeStream.cs b/src/Managing.Infrastructure.Exchanges/ExchangeStream.cs new file mode 100644 index 0000000..eaf0690 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/ExchangeStream.cs @@ -0,0 +1,41 @@ +using Binance.Net.Interfaces.Clients; +using Managing.Application.Abstractions.Services; +using Managing.Application.Shared; +using Managing.Domain.Candles; +using Managing.Infrastructure.Exchanges.Helpers; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges; + +public class ExchangeStream : IExchangeStream +{ + private readonly ILogger logger; + private readonly IBinanceSocketClient _binanceSocketClient; + + public ExchangeStream(IBinanceSocketClient binanceSocketClient, ILogger logger) + { + _binanceSocketClient = binanceSocketClient; + this.logger = logger; + } + + public async Task StartBinanceWorker(Ticker ticker, Func action) + { + logger.LogInformation($"Starting binance worker for {ticker}"); + + await _binanceSocketClient.SpotApi.ExchangeData.SubscribeToKlineUpdatesAsync(BinanceHelpers.ToBinanceTicker(ticker), Binance.Net.Enums.KlineInterval.OneSecond, candle => + { + if (candle.Data.Data?.Final == true) + { + //action((candle) => { CandleHelpers.Map(candle.Data.Data, ticker, Timeframe.FiveMinutes)}); + action(CandleHelpers.Map(candle.Data.Data, ticker, Timeframe.FiveMinutes)); + } + }); + } + + public async Task StopBinanceWorker() + { + logger.LogInformation($"Stoping all Binance worker subscription"); + await _binanceSocketClient.UnsubscribeAllAsync(); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs new file mode 100644 index 0000000..6403980 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/BaseProcessor.cs @@ -0,0 +1,30 @@ +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using Managing.Infrastructure.Exchanges.Abstractions; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Exchanges +{ + public abstract class BaseProcessor : IExchangeProcessor + { + public abstract void LoadClient(Account account); + public abstract Task CancelOrder(Account account, Ticker ticker); + public abstract Enums.TradingExchanges Exchange(); + public abstract Task GetBalance(Account account, bool isForPaperTrading = false); + public abstract Candle GetCandle(Account account, Ticker ticker, DateTime date); + public abstract Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); + public abstract decimal GetFee(Account account, bool isForPaperTrading = false); + public abstract decimal GetPrice(Account account, Ticker ticker, DateTime date); + public abstract Task GetQuantityInPosition(Account account, Ticker ticker); + public abstract Task GetTrade(Account account, string order, Ticker ticker); + public abstract Task> GetTrades(Account account, Ticker ticker); + public abstract decimal GetVolume(Account account, Ticker ticker); + public abstract Task 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 Orderbook GetOrderbook(Account account, Ticker ticker); + public abstract Task> GetBalances(Account account, bool isForPaperTrading = false); + public abstract Task> GetOrders(Account account, Ticker ticker); + public abstract Task GetTrade(string reference, string orderId, Ticker ticker); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs new file mode 100644 index 0000000..4135ee7 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs @@ -0,0 +1,172 @@ +using Binance.Net.Clients; +using Binance.Net.Enums; +using Binance.Net.Interfaces.Clients; +using CryptoExchange.Net.Authentication; +using Managing.Common; +using Managing.Core; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using Managing.Infrastructure.Exchanges.Helpers; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Exchanges; + +public class BinanceProcessor : BaseProcessor +{ + private ILogger _logger; + private IBinanceRestClient _binanceClient; + + public BinanceProcessor(ILogger logger) + { + _logger = logger; + } + + public override async Task CancelOrder(Account account, Ticker ticker) + { + var binanceResult = await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); + return binanceResult.Success; + } + + public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Binance; + + public override async Task GetBalance(Account account, bool isForPaperTrading = false) + { + var balance = 0m; + var binanceBalance = await _binanceClient.UsdFuturesApi.Account.GetBalancesAsync(); + foreach (var item in binanceBalance.Data) + { + balance += item.AvailableBalance; + } + return balance; + } + + public override Task> GetBalances(Account account, bool isForPaperTrading = false) + { + throw new NotImplementedException(); + } + + public override Candle GetCandle(Account account, Ticker ticker, DateTime date) + { + throw new NotImplementedException(); + } + + public override async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync(BinanceHelpers.ToBinanceTicker(ticker), + BinanceHelpers.Map(timeframe), startDate); + + return (List)binanceCandles.Data.Select(binanceKline => BinanceHelpers.Map(binanceKline, ticker, account.Exchange)); + } + + public override decimal GetFee(Account account, bool isForPaperTrading = false) + { + throw new NotImplementedException(); + } + + public override Orderbook GetOrderbook(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override Task> GetOrders(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override decimal GetPrice(Account account, Ticker ticker, DateTime date) + { + var binancePrice = _binanceClient.UsdFuturesApi.ExchangeData.GetPriceAsync(BinanceHelpers.ToBinanceTicker(ticker)).Result.Data; + return binancePrice.Price; + } + + public override async Task GetQuantityInPosition(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override async Task GetTrade(Account account, string order, Ticker ticker) + { + var binanceOrder = await _binanceClient.UsdFuturesApi.Trading.GetOrderAsync(BinanceHelpers.ToBinanceTicker(ticker), origClientOrderId: order); + return BinanceHelpers.Map(binanceOrder.Data); + } + + public override Task GetTrade(string reference, string orderId, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override async Task> GetTrades(Account account, Ticker ticker) + { + var binanceOrder = + await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker)); + return (List)binanceOrder.Data.Select(o => BinanceHelpers.Map(o)); + } + + public override decimal GetVolume(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override void LoadClient(Account account) + { + var credentials = new ApiCredentials(account.Key, account.Secret); + _binanceClient = new BinanceRestClient((options) => { options.ApiCredentials = credentials; }); + } + + public override async Task 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) + { + 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)); + + if (trade.Quantity <= 0) + throw new InvalidOperationException( + $"Minimum quantity not match, cannot execute trade {direction} for {ticker}."); + + var binanceOrderType = BinanceHelpers.BinanceOrderTypeMap(tradeType); + var binanceResult = await _binanceClient.UsdFuturesApi.Trading.PlaceOrderAsync( + BinanceHelpers.ToBinanceTicker(ticker), + direction != TradeDirection.Long ? OrderSide.Sell : OrderSide.Buy, + binanceOrderType, + price: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null, + quantity: trade.Quantity, + reduceOnly: reduceOnly, + priceProtect: true, + timeInForce: binanceOrderType == FuturesOrderType.Limit ? TimeInForce.GoodTillCanceled : null, + activationPrice: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null, + stopPrice: binanceOrderType == FuturesOrderType.StopMarket ? trade.Price : null); + + _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(binanceResult)); + + var binanceOrderExecuted = BinanceHelpers.Map(binanceResult, leverage); + + if (binanceResult.Success) + { + var binanceOrder = await GetTrade(account, binanceOrderExecuted.ExchangeOrderId, ticker); + + trade.Price = binanceOrder.Price; + trade.SetStatus(binanceOrder.Status); + trade.SetExchangeOrderId(binanceOrderExecuted.ExchangeOrderId); + trade.SetMessage(binanceOrderExecuted.Message); + } + return trade; + } + + private int GetPricePrecision(Account account, Ticker ticker) + { + var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data; + var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker)) + .PriceFilter.TickSize; + return MathHelpers.GetDecimalPlaces(precision); + } + + 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; + return Convert.ToInt32(precision); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs new file mode 100644 index 0000000..8f2a4a5 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs @@ -0,0 +1,172 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Evm; +using Managing.Domain.Trades; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Exchanges; + +public class EvmProcessor : BaseProcessor +{ + public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Evm; + + private ILogger _logger; + private IEvmManager _evmManager; + + public EvmProcessor(ILogger logger, IEvmManager evmManager) + { + _logger = logger; + _evmManager = evmManager; + } + + public override async Task CancelOrder(Account account, Ticker ticker) + { + return await _evmManager.CancelOrders(account, ticker); + } + + + public override async Task GetBalance(Account account, bool isForPaperTrading = false) + { + //var balances = await _evmManager.GetAllBalancesOnAllChain(account.Key); + //var balanceAmount = 0m; + + //foreach (var balance in balances) + //{ + // balanceAmount += balance.Value; + //} + + var evmBalance = await _evmManager.GetTokenBalance(Constants.Chains.Arbitrum, Ticker.USDC, account.Key); + return evmBalance.Balance; + } + + public override async Task> GetBalances(Account account, bool isForPaperTrading = false) + { + var balances = await _evmManager.GetAllBalancesOnAllChain(account.Key); + return Map(balances); + } + + private List Map(List balances) + { + return balances.ConvertAll(balance => new Balance + { + TokenName = balance.TokenName, + Price = balance.Price, + Value = balance.Value, + Amount = balance.Balance, + TokenAdress = balance.TokenAddress, + Chain = balance.Chain + }); + } + + public override Candle GetCandle(Account account, Ticker ticker, DateTime date) + { + return _evmManager.GetCandle(SubgraphProvider.Gbc, ticker).Result; + } + + public override async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval) + { + return await _evmManager.GetCandles(SubgraphProvider.Gbc, ticker, startDate, interval); + } + + public override decimal GetFee(Account account, bool isForPaperTrading = false) + { + return _evmManager.GetFee(Constants.Chains.Arbitrum).Result; + } + + public override decimal GetPrice(Account account, Ticker ticker, DateTime date) + { + return GetCandle(account, ticker, date).Close; + } + + public override async Task GetQuantityInPosition(Account account, Ticker ticker) + { + return await _evmManager.QuantityInPosition(Constants.Chains.Arbitrum, account.Key, ticker); + } + + public override async Task GetTrade(Account account, string order, Ticker ticker) + { + return await _evmManager.GetTrade(account, Constants.Chains.Arbitrum, ticker); + } + + public override async Task GetTrade(string reference, string orderId, Ticker ticker) + { + return await _evmManager.GetTrade(reference, Constants.Chains.Arbitrum, ticker); + } + + public override decimal GetVolume(Account account, Ticker ticker) + { + var volume = _evmManager.GetVolume(SubgraphProvider.ChainlinkPrice, ticker); + return volume; + } + + public override async Task 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) + { + Trade trade; + if (reduceOnly) + { + if (tradeType is TradeType.TakeProfit + or TradeType.StopLoss) + { + // If trade type is TP or SL we create DecreaseOrder + trade = await _evmManager.DecreaseOrder(account, tradeType, ticker, direction, price, quantity, leverage); + } + else + { + // Trade requested is not an SL nor TP + // Price is the current price + // We create Decrease position + trade = await _evmManager.DecreasePosition(account, ticker, direction, price, quantity, leverage); + } + } + else + { + trade = await _evmManager.IncreasePosition(account, ticker, direction, price, quantity, leverage); + } + + return trade; + } + + public override async Task> 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(); + } + + public override async Task> GetTrades(Account account, Ticker ticker) + { + // Use by commandHandler to get trades list + throw new NotImplementedException(); + } + + public override Orderbook GetOrderbook(Account account, Ticker ticker) + { + // Not use because EVM do not based on an orderbook + throw new NotImplementedException(); + } + + #endregion +} diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs new file mode 100644 index 0000000..634d59e --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs @@ -0,0 +1,199 @@ +using CryptoExchange.Net.Authentication; +using FTX.Net.Clients; +using FTX.Net.Interfaces.Clients; +using FTX.Net.Objects; +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using Managing.Infrastructure.Exchanges.Helpers; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Exchanges; + +public class FtxProcessor : BaseProcessor +{ + private IFTXClient _ftxClient; + private ILogger _logger; + + public FtxProcessor(ILogger logger) + { + _logger = logger; + } + + public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Ftx; + + public override void LoadClient(Account account) + { + var credentials = new ApiCredentials(account.Key, account.Secret); + + var ftxConfig = new FTXClientOptions() + { + SubaccountName = account.Name + }; + _ftxClient = new FTXClient(ftxConfig); + } + + + public override async Task CancelOrder(Account account, Ticker ticker) + { + LoadClient(account); + var ftxResult = await _ftxClient.TradeApi.Trading.CancelAllOrdersAsync(FtxHelpers.ToFtxTicker(ticker)); + return ftxResult.Success; + } + + public override async Task GetBalance(Account account, bool isForPaperTrading = false) + { + LoadClient(account); + + var balance = 0m; + var ftxBalance = await _ftxClient.TradeApi.Account.GetBalancesAsync(); + foreach (var item in ftxBalance.Data) + { + balance += item.UsdValue; + } + return balance; + } + + public override Candle GetCandle(Account account, Ticker ticker, DateTime date) + { + LoadClient(account); + + var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), + FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; + if (ftxKlines != null && ftxKlines.Any()) + { + var lastCandle = ftxKlines.ToList().LastOrDefault(); + return CandleHelpers.Map(lastCandle); + } + + return null; + } + + public override async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + LoadClient(account); + + var candles = new List(); + var ftxCandles = await _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), + FtxHelpers.Map(timeframe), startDate); + + if (ftxCandles.Success) + candles.AddRange(ftxCandles.Data.SkipLast(1).Select(ftxKline => FtxHelpers.Map(ftxKline, ticker, account.Exchange, timeframe))); + + return candles; + } + + public override decimal GetFee(Account account, bool isForPaperTrading = false) + { + LoadClient(account); + var ftxResult = _ftxClient.TradeApi.Account.GetAccountInfoAsync().Result.Data; + return ftxResult.TakerFee; + } + + public override decimal GetPrice(Account account, Ticker ticker, DateTime date) + { + LoadClient(account); + var ftxKlines = _ftxClient.TradeApi.ExchangeData.GetKlinesAsync(FtxHelpers.ToFtxTicker(ticker), + FTX.Net.Enums.KlineInterval.OneMinute, date.AddHours(-2.5)).Result.Data; + if (ftxKlines != null && ftxKlines.Any()) + { + return ftxKlines.ToList().LastOrDefault().ClosePrice; + } + return 0; + } + + public override async Task GetQuantityInPosition(Account account, Ticker ticker) + { + var ftxTickerBalance = _ftxClient.TradeApi.Account.GetPositionsAsync().Result.Data; + return ftxTickerBalance.FirstOrDefault(f => f.Future == FtxHelpers.ToFtxTicker(ticker)).Quantity; + } + + public override async Task GetTrade(Account account, string order, Ticker ticker) + { + LoadClient(account); + var ftxOrder = await _ftxClient.TradeApi.Trading.GetOrderByClientOrderIdAsync(order); + return FtxHelpers.Map(ftxOrder.Data); + } + + public override async Task> GetTrades(Account account, Ticker ticker) + { + LoadClient(account); + var ftxOrder = await _ftxClient.TradeApi.Trading.GetOrdersAsync(ticker.ToString()); + return (List)ftxOrder.Data.Select(o => FtxHelpers.Map(o)); + } + + public override decimal GetVolume(Account account, Ticker ticker) + { + var futureStats = _ftxClient.TradeApi.ExchangeData.GetFutureStatsAsync(FtxHelpers.ToFtxTicker(ticker)).Result.Data; + return futureStats.Volume; + } + + public override async Task 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) + { + LoadClient(account); + + 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) + { + 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); + _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult)); + ftxOrder = FtxHelpers.Map(ftxResult, leverage); + } + else + { + var ftxOrderType = FtxHelpers.FtxOrderTypeMap(tradeType); + var ftxResult = await _ftxClient.TradeApi.Trading.PlaceOrderAsync(FtxHelpers.ToFtxTicker(ticker), + direction != TradeDirection.Long ? FTX.Net.Enums.OrderSide.Sell : FTX.Net.Enums.OrderSide.Buy, + ftxOrderType, + price: ftxOrderType == FTX.Net.Enums.OrderType.Limit ? price : null, + quantity: quantity, + clientOrderId: Guid.NewGuid().ToString(), + immediateOrCancel: ioc); + _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(ftxResult)); + ftxOrder = FtxHelpers.Map(ftxResult, leverage); + } + + trade.SetStatus(ftxOrder.Status); + trade.SetExchangeOrderId(ftxOrder.ExchangeOrderId); + trade.SetMessage(ftxOrder.Message); + trade.Price = ftxOrder.Price; + + return trade; + } + + public override Orderbook GetOrderbook(Account account, Ticker ticker) + { + LoadClient(account); + var ftxOrderBook = _ftxClient.TradeApi.ExchangeData.GetOrderBookAsync(FtxHelpers.ToFtxTicker(ticker), 100).Result; + return FtxHelpers.Map(ftxOrderBook); + } + + public override Task> GetBalances(Account account, bool isForPaperTrading = false) + { + throw new NotImplementedException(); + } + + public override Task> GetOrders(Account acount, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override Task GetTrade(string reference, string orderId, Ticker ticker) + { + throw new NotImplementedException(); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs new file mode 100644 index 0000000..4309a44 --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/KrakenProcessor.cs @@ -0,0 +1,138 @@ +using CryptoExchange.Net.Authentication; +using Kraken.Net.Clients; +using Kraken.Net.Interfaces.Clients; +using Kraken.Net.Objects.Options; +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using Managing.Infrastructure.Exchanges.Helpers; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Exchanges; + +public class KrakenProcessor : BaseProcessor +{ + private ILogger _logger; + private IKrakenRestClient _krakenClient; + + public KrakenProcessor(ILogger logger) + { + _logger = logger; + } + public override Task CancelOrder(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Kraken; + + public override async Task GetBalance(Account account, bool isForPaperTrading = false) + { + LoadClient(account); + var balance = await _krakenClient.SpotApi.Account.GetBalancesAsync(); + balance.Data.TryGetValue("USDT", out decimal krakenBalance); + return krakenBalance; + } + + public override Task> GetBalances(Account account, bool isForPaperTrading = false) + { + throw new NotImplementedException(); + } + + public override Candle GetCandle(Account account, Ticker ticker, DateTime date) + { + throw new NotImplementedException(); + } + + public override Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval) + { + throw new NotImplementedException(); + } + + public override decimal GetFee(Account account, bool isForPaperTrading = false) + { + throw new NotImplementedException(); + } + + public override Orderbook GetOrderbook(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override Task> GetOrders(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override decimal GetPrice(Account account, Ticker ticker, DateTime date) + { + LoadClient(account); + var krakenKline = _krakenClient.SpotApi.ExchangeData.GetKlinesAsync(ticker.ToString(), + Kraken.Net.Enums.KlineInterval.OneMinute, date).Result.Data.Data.ToList()[0]; + return (krakenKline.HighPrice + krakenKline.ClosePrice) / 2; + } + + public override Task GetQuantityInPosition(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override async Task GetTrade(Account account, string order, Ticker ticker) + { + LoadClient(account); + var krakenOrder = await _krakenClient.SpotApi.Trading.GetOrderAsync(order); + return KrakenHelpers.Map(krakenOrder.Data.Values.First()); + } + + public override Task GetTrade(string reference, string orderId, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override async Task> GetTrades(Account account, Ticker ticker) + { + LoadClient(account); + var krakenOrder = await _krakenClient.SpotApi.Trading.GetOrdersAsync(); + return (List)krakenOrder.Data.Select(o => KrakenHelpers.Map(o)); + } + + public override decimal GetVolume(Account account, Ticker ticker) + { + throw new NotImplementedException(); + } + + public override void LoadClient(Account account) + { + var credentials = new ApiCredentials(account.Key, account.Secret); + var krakenConfig = new KrakenRestOptions() + { + ApiCredentials = credentials + }; + _krakenClient = new KrakenRestClient((options) => { options.ApiCredentials = krakenConfig.ApiCredentials; }); + } + + public override async Task 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) + { + LoadClient(account); + var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price, leverage, "", ""); + trade.SetQuantity(quantity, 6); + trade.SetPrice(price, 1); + + var order = await _krakenClient.SpotApi.Trading.PlaceOrderAsync(ticker.ToString(), + direction != TradeDirection.Long ? Kraken.Net.Enums.OrderSide.Sell : Kraken.Net.Enums.OrderSide.Buy, + KrakenHelpers.KrakenOrderTypeMap(tradeType), + price: price, + quantity: quantity, + leverage: leverage); + + _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(order)); + var krakenOrderExecuted = GetTrade(account, ((string[])order.Data.OrderIds)[0], ticker).Result; + trade.SetStatus(krakenOrderExecuted.Status); + trade.SetExchangeOrderId(krakenOrderExecuted.ExchangeOrderId); + trade.SetMessage(krakenOrderExecuted.Message); + return trade; + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs b/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs new file mode 100644 index 0000000..ea5aeeb --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Helpers/BinanceHelpers.cs @@ -0,0 +1,195 @@ +using Binance.Net.Enums; +using Binance.Net.Interfaces; +using Binance.Net.Objects.Models.Futures; +using CryptoExchange.Net.Objects; +using Managing.Common; +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Helpers +{ + public static class BinanceHelpers + { + public static Trade Map(BinanceFuturesOrder data) + { + if (data == null) + return null; + + return new Trade(data.CreateTime, + (data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short, + (TradeStatus)data.Status, (TradeType)data.OriginalType, MiscExtensions.ParseEnum(data.Symbol), data.Quantity, data.AvgPrice, 1, + data.ClientOrderId, ""); + } + + public static FuturesOrderType BinanceOrderTypeMap(TradeType tradeType) + { + switch (tradeType) + { + case TradeType.Limit: + return FuturesOrderType.Limit; + case TradeType.Market: + return FuturesOrderType.Market; + case TradeType.StopMarket: + return FuturesOrderType.StopMarket; + case TradeType.StopLossLimit: + return FuturesOrderType.Stop; + default: + return FuturesOrderType.Limit; + } + } + + public static TradeType BinanceOrderTradeType(FuturesOrderType orderType) + { + switch (orderType) + { + case FuturesOrderType.Stop: + case FuturesOrderType.Limit: + return TradeType.Limit; + case FuturesOrderType.Market: + return TradeType.Market; + case FuturesOrderType.StopMarket: + return TradeType.StopMarket; + default: + return TradeType.Limit; + } + } + + public static Trade Map(WebCallResult result, decimal? leverage = null) + { + var data = result.Data; + + if (data == null) + { + return new Trade(DateTime.Now, TradeDirection.None, + TradeStatus.Cancelled, TradeType.Market, Ticker.BTC, 0, 0, 0, + "", result.Error?.Message); + } + + return new Trade(data.UpdateTime, + (data.PositionSide == PositionSide.Long) ? TradeDirection.Long : TradeDirection.Short, + (TradeStatus)data.Status, (TradeType)data.OriginalType, MiscExtensions.ParseEnum(data.Symbol), data.Quantity, data.Price, leverage, + data.ClientOrderId, ""); + } + + public static Candle Map(IBinanceKline binanceKline, Ticker ticker, Enums.TradingExchanges exchange) + { + return new Candle + { + Date = binanceKline.CloseTime, + BaseVolume = binanceKline.Volume, + Close = binanceKline.ClosePrice, + High = binanceKline.HighPrice, + Low = binanceKline.LowPrice, + Open = binanceKline.OpenPrice, + Ticker = ticker.ToString(), + QuoteVolume = binanceKline.QuoteVolume, + TradeCount = binanceKline.TradeCount, + OpenTime = binanceKline.OpenTime, + TakerBuyBaseVolume = binanceKline.TakerBuyBaseVolume, + TakerBuyQuoteVolume = binanceKline.TakerBuyQuoteVolume, + Exchange = exchange + }; + } + + internal static KlineInterval Map(Timeframe interval) => interval switch + { + Timeframe.FiveMinutes => KlineInterval.FiveMinutes, + Timeframe.FifteenMinutes => KlineInterval.FifteenMinutes, + Timeframe.ThirtyMinutes => KlineInterval.ThirtyMinutes, + Timeframe.OneHour => KlineInterval.OneHour, + Timeframe.FourHour => KlineInterval.FourHour, + Timeframe.OneDay => KlineInterval.OneDay, + _ => throw new NotImplementedException(), + }; + + public static string ToBinanceTicker(Ticker ticker) + { + switch (ticker) + { + case Ticker.ADA: + return "ADAUSDT"; + case Ticker.ALGO: + return "ALGOUSDT"; + case Ticker.ATOM: + return "ATOMUSDT"; + case Ticker.AVAX: + return "AVAXUSDT"; + case Ticker.BAT: + return "BATUSDT"; + case Ticker.BNB: + return "BNBUSDT"; + case Ticker.BTC: + return "BTCUSDT"; + case Ticker.CRV: + return "CRVUSDT"; + case Ticker.DEFI: + return "DEFIUSDT"; + case Ticker.DOGE: + return "DOGEUSDT"; + case Ticker.DOT: + return "DOTUSDT"; + case Ticker.DYDX: + return "DYDXUSDT"; + case Ticker.ETC: + return "ETCUSDT"; + case Ticker.ETH: + return "ETHUSDT"; + case Ticker.FTM: + return "FTMUSDT"; + case Ticker.GALA: + return "GALAUSDT"; + case Ticker.GRT: + return "GRTUSDT"; + case Ticker.IMX: + return "IMXUSDT"; + case Ticker.KAVA: + return "KAVAUSDT"; + case Ticker.KSM: + return "KSMUSDT"; + case Ticker.LINK: + return "LINKUSDT"; + case Ticker.LRC: + return "LRCUSDT"; + case Ticker.LTC: + return "LTCUSDT"; + case Ticker.MATIC: + return "MATICUSDT"; + case Ticker.MKR: + return "MKRUSDT"; + case Ticker.NEAR: + return "NEARUSDT"; + case Ticker.NEO: + return "NEOUSDT"; + case Ticker.ONT: + return "ONTUSDT"; + case Ticker.SAND: + return "SANDUSDT"; + case Ticker.SOL: + return "SOLUSDT"; + case Ticker.SRM: + return "SRMUSDT"; + case Ticker.SUSHI: + return "SUSHIUSDT"; + case Ticker.THETA: + return "THETAUSDT"; + case Ticker.UNI: + return "UNIUSDT"; + case Ticker.WAVES: + return "WAVESUSDT"; + case Ticker.XMR: + return "XMRUSDT"; + case Ticker.XRP: + return "XRPUSDT"; + case Ticker.XTZ: + return "XTZUSDT"; + case Ticker.ZEC: + return "ZECUSDT"; + default: + break; + } + throw new NotImplementedException(); + } + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs b/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs new file mode 100644 index 0000000..3fcfe1c --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Helpers/FtxHelpers.cs @@ -0,0 +1,286 @@ +using CryptoExchange.Net.Objects; +using FTX.Net.Enums; +using FTX.Net.Objects.Models; +using Managing.Common; +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Helpers +{ + public static class FtxHelpers + { + public static string ToFtxTicker(Ticker ticker) + { + switch (ticker) + { + case Ticker.ADA: + return "ADA-PERP"; + case Ticker.APE: + return "APE-PERP"; + case Ticker.ALICE: + return "ALICE-PERP"; + case Ticker.ALGO: + return "ALGO-PERP"; + case Ticker.ATOM: + return "ATOM-PERP"; + case Ticker.AVAX: + return "AVAX-PERP"; + case Ticker.AXS: + return "AXS-PERP"; + case Ticker.BAT: + return "BAT-PERP"; + case Ticker.BNB: + return "BNB-PERP"; + case Ticker.BTC: + return "BTC-PERP"; + case Ticker.BAL: + return "BAL-PERP"; + case Ticker.C98: + return "C98-PERP"; + case Ticker.CHR: + return "CHR-PERP"; + case Ticker.CHZ: + return "CHZ-PERP"; + case Ticker.COMP: + return "COMP-PERP"; + case Ticker.CRO: + return "CRO-PERP"; + case Ticker.CRV: + return "CRV-PERP"; + case Ticker.CVC: + return "CVC-PERP"; + case Ticker.DEFI: + return "DEFI-PERP"; + case Ticker.DOGE: + return "DOGE-PERP"; + case Ticker.DOT: + return "DOT-PERP"; + case Ticker.DYDX: + return "DYDX-PERP"; + case Ticker.ENS: + return "ENS-PERP"; + case Ticker.ETC: + return "ETC-PERP"; + case Ticker.ETH: + return "ETH-PERP"; + case Ticker.FIL: + return "FIL-PERP"; + case Ticker.FLM: + return "FLM-PERP"; + case Ticker.FTM: + return "FTM-PERP"; + case Ticker.GALA: + return "GALA-PERP"; + case Ticker.GMT: + return "GMT-PERP"; + case Ticker.GRT: + return "GRT-PERP"; + case Ticker.HNT: + return "HNT-PERP"; + case Ticker.IMX: + return "IMX-PERP"; + case Ticker.JASMY: + return "JASMY-PERP"; + case Ticker.KAVA: + return "KAVA-PERP"; + case Ticker.KSM: + return "KSM-PERP"; + case Ticker.LDO: + return "LDO-PERP"; + case Ticker.LINK: + return "LINK-PERP"; + case Ticker.LOOKS: + return "LOOKS-PERP"; + case Ticker.LRC: + return "LRC-PERP"; + case Ticker.LTC: + return "LTC-PERP"; + case Ticker.MANA: + return "MANA-PERP"; + case Ticker.MATIC: + return "MATIC-PERP"; + case Ticker.MKR: + return "MKR-PERP"; + case Ticker.NEAR: + return "NEAR-PERP"; + case Ticker.NEO: + return "NEO-PERP"; + case Ticker.OMG: + return "OMG-PERP"; + case Ticker.ONE: + return "ONE-PERP"; + case Ticker.ONT: + return "ONT-PERP"; + case Ticker.QTUM: + return "QTUM-PERP"; + case Ticker.REEF: + return "REEF-PERP"; + case Ticker.REN: + return "REN-PERP"; + case Ticker.ROSE: + return "ROSE-PERP"; + case Ticker.RSR: + return "RSR-PERP"; + case Ticker.RUNE: + return "RUNE-PERP"; + case Ticker.SAND: + return "SAND-PERP"; + case Ticker.SOL: + return "SOL-PERP"; + case Ticker.SRM: + return "SRM-PERP"; + case Ticker.STMX: + return "STMX-PERP"; + case Ticker.SUSHI: + return "SUSHI-PERP"; + case Ticker.SXP: + return "SXP-PERP"; + case Ticker.THETA: + return "THETA-PERP"; + case Ticker.UNI: + return "UNI-PERP"; + case Ticker.VET: + return "VET-PERP"; + case Ticker.WAVES: + return "WAVES-PERP"; + case Ticker.XMR: + return "XMR-PERP"; + case Ticker.XRP: + return "XRP-PERP"; + case Ticker.XTZ: + return "XTZ-PERP"; + case Ticker.YFI: + return "YFI-PERP"; + case Ticker.ZEC: + return "ZEC-PERP"; + case Ticker.ZIL: + return "ZIL-PERP"; + default: + break; + } + throw new NotImplementedException(); + } + + + public static Trade Map(WebCallResult result, decimal? leverage = null) + { + var data = result.Data; + + if (data == null) + { + return new Trade(DateTime.Now, TradeDirection.None, + TradeStatus.Cancelled, TradeType.Market, Ticker.BTC, 0, 0, 0, + "", result.Error?.Message); + } + + return new Trade(data.CreateTime, + (data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short, + (TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum(data.Symbol), data.Quantity, data.AverageFillPrice ?? 0, leverage, + data.ClientOrderId, ""); + } + + internal static Trade Map(WebCallResult ftxResult, decimal? leverage) + { + var data = ftxResult.Data; + + if (data == null) + { + return new Trade(DateTime.Now, TradeDirection.None, + TradeStatus.Cancelled, TradeType.Market, Ticker.BTC, 0, 0, 0, + "", ftxResult.Error?.Message); + } + + return new Trade(data.CreateTime, + (data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short, + (TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum(data.Symbol), data.Quantity, data.TriggerPrice ?? 0, leverage, + Guid.NewGuid().ToString(), ""); + } + + public static OrderType FtxOrderTypeMap(TradeType tradeType) + { + switch (tradeType) + { + case TradeType.Limit: + return OrderType.Limit; + case TradeType.Market: + return OrderType.Market; + default: + return OrderType.Limit; + } + } + + public static Trade Map(FTXOrder data) + { + if (data == null) + return null; + + return new Trade(data.CreateTime, TradeDirection.None, + (TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum(data.Symbol), data.Quantity, data.AverageFillPrice ?? 0, 0, + data.ClientOrderId, ""); + } + + public static Candle Map( + FTXKline ftxKline, + Ticker ticker, + Enums.TradingExchanges exchange, + Timeframe timeframe) + { + return new Candle + { + Date = ftxKline.OpenTime, + BaseVolume = ftxKline.Volume ?? 0, + Close = ftxKline.ClosePrice, + High = ftxKline.HighPrice, + Low = ftxKline.LowPrice, + Open = ftxKline.OpenPrice, + Ticker = ticker.ToString(), + OpenTime = ftxKline.OpenTime, + Exchange = exchange, + Timeframe = timeframe + }; + } + + internal static KlineInterval Map(Timeframe interval) => interval switch + { + Timeframe.FiveMinutes => KlineInterval.FiveMinutes, + Timeframe.FifteenMinutes => KlineInterval.FifteenMinutes, + Timeframe.OneHour => KlineInterval.OneHour, + Timeframe.FourHour => KlineInterval.FourHours, + Timeframe.OneDay => KlineInterval.OneDay, + _ => throw new NotImplementedException(), + }; + + internal static TriggerOrderType FtxTriggerOrderTypeMap(TradeType tradeType) => tradeType switch + { + TradeType.StopMarket => TriggerOrderType.Stop, + TradeType.StopLimit => TriggerOrderType.Stop, + TradeType.StopLoss => TriggerOrderType.Stop, + TradeType.TakeProfit => TriggerOrderType.TakeProfit, + TradeType.StopLossProfit => TriggerOrderType.Stop, + TradeType.StopLossProfitLimit => TriggerOrderType.Stop, + TradeType.StopLossLimit => TriggerOrderType.Stop, + TradeType.TakeProfitLimit => TriggerOrderType.TakeProfit, + TradeType.TrailingStop => TriggerOrderType.TrailingStop, + TradeType.TrailingStopLimit => TriggerOrderType.TrailingStop, + TradeType.StopLossAndLimit => TriggerOrderType.Stop, + TradeType.SettlePosition => TriggerOrderType.Stop, + _ => throw new NotImplementedException(), + }; + + internal static Orderbook Map(WebCallResult ftxOrderBook) + { + return new Orderbook() + { + Asks = Map(ftxOrderBook.Data.Asks), + Bids = Map(ftxOrderBook.Data.Bids) + }; + } + + private static List Map(IEnumerable entry) + { + return entry.Select(ask => new OrderBookEntry() { Price = ask.Price, Quantity = ask.Quantity}).ToList(); + } + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Helpers/KrakenHelpers.cs b/src/Managing.Infrastructure.Exchanges/Helpers/KrakenHelpers.cs new file mode 100644 index 0000000..3abbb2e --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Helpers/KrakenHelpers.cs @@ -0,0 +1,46 @@ +using Kraken.Net.Objects.Models; +using Managing.Core; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Exchanges.Helpers; + +public static class KrakenHelpers +{ + public static Trade Map(KrakenOrder order) + { + var leverageParse = order.OrderDetails.Leverage.Split((char)':')[0]; + long.TryParse(leverageParse, out long leverage); + + return new Trade(order.CreateTime, + TradeDirection.None, + (TradeStatus)order.Status, + (TradeType)order.OrderDetails.Type, + MiscExtensions.ParseEnum(order.OrderDetails.Symbol), + order.Quantity, + order.AveragePrice, + leverage, + order.ClientOrderId, + ""); + } + + public static Kraken.Net.Enums.OrderType KrakenOrderTypeMap(TradeType tradeType) + { + switch (tradeType) + { + case TradeType.Limit: + return Kraken.Net.Enums.OrderType.Limit; + case TradeType.Market: + return Kraken.Net.Enums.OrderType.Market; + case TradeType.StopMarket: + return Kraken.Net.Enums.OrderType.StopMarket; + default: + return Kraken.Net.Enums.OrderType.Limit; + } + } + + internal static Trade Map(KeyValuePair o) + { + throw new NotImplementedException(); + } +} diff --git a/src/Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj b/src/Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj new file mode 100644 index 0000000..936d0fe --- /dev/null +++ b/src/Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs b/src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs new file mode 100644 index 0000000..ae85208 --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs @@ -0,0 +1,142 @@ +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using System.Reflection; + +namespace Managing.Infrastructure.Messengers.Discord +{ + public class CommandHandler + { + private readonly DiscordSocketClient _client; + private readonly InteractionService _commands; + private readonly IServiceProvider _services; + + public CommandHandler(DiscordSocketClient client, InteractionService commands, IServiceProvider services) + { + _client = client; + _commands = commands; + _services = services; + } + + public async Task InitializeAsync() + { + // add the public modules that inherit InteractionModuleBase to the InteractionService + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + + // process the InteractionCreated payloads to execute Interactions commands + _client.InteractionCreated += HandleInteraction; + + // process the command execution results + _commands.SlashCommandExecuted += SlashCommandExecuted; + _commands.ContextCommandExecuted += ContextCommandExecuted; + _commands.ComponentCommandExecuted += ComponentCommandExecuted; + } + + private Task ComponentCommandExecuted(ComponentCommandInfo arg1, IInteractionContext arg2, IResult arg3) + { + if (!arg3.IsSuccess) + { + switch (arg3.Error) + { + case InteractionCommandError.UnmetPrecondition: + // implement + break; + case InteractionCommandError.UnknownCommand: + // implement + break; + case InteractionCommandError.BadArgs: + // implement + break; + case InteractionCommandError.Exception: + // implement + break; + case InteractionCommandError.Unsuccessful: + // implement + break; + default: + break; + } + } + + return Task.CompletedTask; + } + + private Task ContextCommandExecuted(ContextCommandInfo arg1, IInteractionContext arg2, IResult arg3) + { + if (!arg3.IsSuccess) + { + switch (arg3.Error) + { + case InteractionCommandError.UnmetPrecondition: + // implement + break; + case InteractionCommandError.UnknownCommand: + // implement + break; + case InteractionCommandError.BadArgs: + // implement + break; + case InteractionCommandError.Exception: + // implement + break; + case InteractionCommandError.Unsuccessful: + // implement + break; + default: + break; + } + } + + return Task.CompletedTask; + } + + private Task SlashCommandExecuted(SlashCommandInfo arg1, IInteractionContext arg2, IResult arg3) + { + if (!arg3.IsSuccess) + { + switch (arg3.Error) + { + case InteractionCommandError.UnmetPrecondition: + // implement + break; + case InteractionCommandError.UnknownCommand: + // implement + break; + case InteractionCommandError.BadArgs: + // implement + break; + case InteractionCommandError.Exception: + // implement + break; + case InteractionCommandError.Unsuccessful: + // implement + break; + default: + break; + } + } + + return Task.CompletedTask; + } + + private async Task HandleInteraction(SocketInteraction arg) + { + try + { + // create an execution context that matches the generic type parameter of your InteractionModuleBase modules + var ctx = new SocketInteractionContext(_client, arg); + await _commands.ExecuteCommandAsync(ctx, _services); + } + catch (Exception ex) + { + Console.WriteLine(ex); + // if a Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original + // response, or at least let the user know that something went wrong during the command execution. + if (arg.Type == InteractionType.ApplicationCommand) + { + await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); + } + } + } + } +} diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs new file mode 100644 index 0000000..7a7e582 --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs @@ -0,0 +1,223 @@ +using Discord; +using Discord.Commands; +using Managing.Application.Abstractions; +using Managing.Application.Trading.Commands; +using Managing.Common; +using Managing.Core; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Trades; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Messengers.Discord +{ + public class DiscordCommands : ModuleBase + { + private readonly DiscordSettings _settings; + private readonly IBacktester _backtester; + private readonly string[] _separator = new[] { " " }; + private readonly string _messagePrefix = "Bro, "; + private readonly ICommandHandler _openTradeCommandHandler; + + public DiscordCommands(DiscordSettings settings, + IBacktester backtester, + ICommandHandler openTradeCommandHandler) + { + _settings = settings; + _backtester = backtester; + _openTradeCommandHandler = openTradeCommandHandler; + } + + + [Command("openposition")] + public async Task OpenPosition([Remainder] string rest) + { + Typing(); + + var parameters = rest.Split(_separator, StringSplitOptions.None); + + var exchange = MiscExtensions.ParseEnum(parameters[0]); + var ticker = parameters[1]; + var direction = MiscExtensions.ParseEnum(parameters[2]); + var timeframe = MiscExtensions.ParseEnum(parameters[3]); + var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G"); + + var builder = new ComponentBuilder().WithButton("Open Position", + $"{Constants.DiscordButtonAction.OpenPosition}-{exchange}-{ticker}-{direction}-{timeframe}-{expirationDate}"); + await Context.Channel.SendMessageAsync(_messagePrefix + $"you can now click on this button to open a position on {ticker}" + , components: builder.Build()); + } + + [Command("open")] + public async Task Open([Remainder] string rest) + { + await Context.Channel.SendMessageAsync("Let met few seconds to open a position"); + + Typing(); + try + { + var parameters = rest.Split(_separator, StringSplitOptions.None); + var accountName = parameters[0]; + var ticker = MiscExtensions.ParseEnum(parameters[1]); + var direction = MiscExtensions.ParseEnum(parameters[2]); + var riskLevel = MiscExtensions.ParseEnum(parameters[3]); + var timeframe = MiscExtensions.ParseEnum(parameters[4]); + decimal? stopLoss = parameters[5] != null ? Convert.ToDecimal(parameters[5]) : null; + decimal? takeProfit = parameters[6] != null ? Convert.ToDecimal(parameters[6]) : null; + + var moneymanagement = new MoneyManagement + { + BalanceAtRisk = 5, + StopLoss = stopLoss.GetValueOrDefault(), + TakeProfit = takeProfit.GetValueOrDefault(), + + }; + var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker, PositionInitiator.User, DateTime.UtcNow); + var result = await _openTradeCommandHandler.Handle(tradeCommand); + var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}"); + await Context.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(result), + components: builder.Build()); + } + catch (Exception ex) + { + await Context.Channel.SendMessageAsync($"Something weird happen bro, {ex.Message}"); + } + + } + + [Command("bot")] + public async Task Bot([Remainder] string rest) + { + Typing(); + + if (string.Equals(rest, "enable", StringComparison.OrdinalIgnoreCase)) + { + _settings.BotEnabled = true; + } + if (string.Equals(rest, "disable", StringComparison.OrdinalIgnoreCase)) + { + _settings.BotEnabled = false; + } + await Context.Channel.SendMessageAsync("Bot is " + + (_settings.BotEnabled ? "enabled" : "disabled")); + } + + //[Command("backtest")] + //public async Task Backtest([Remainder] string rest) + //{ + // Typing(); + + // var parameters = rest.Split(_separator, StringSplitOptions.None); + // var ticker = MiscExtensions.ParseEnum(parameters[0]); + // var exchange = MiscExtensions.ParseEnum(parameters[1]); + // var timeframe = MiscExtensions.ParseEnum(parameters[2]); + // var days = parameters[3]; + // var scenario = new Scenario("ScalpingScenario"); + // var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 14); + // scenario.AddStrategy(strategy); + // var account = new Account() { Exchange = exchange }; + + // var backtestResult = _backtester.RunScalpingBotBacktest(account, ticker, scenario, timeframe, Convert.ToDouble(days), 1000); + // await Context.Channel.SendMessageAsync(backtestResult.GetStringReport()); + //} + + [Command("run")] + public async Task Run([Remainder] string rest) + { + Typing(); + var parameters = rest.Split(_separator, StringSplitOptions.None); + var botType = MiscExtensions.ParseEnum(parameters[0]); + var accountName = MiscExtensions.ParseEnum(parameters[1]); + var botName = parameters[2]; + var ticker = MiscExtensions.ParseEnum(parameters[3]); + var timeframe = MiscExtensions.ParseEnum(parameters[4]); + + var message = _messagePrefix; + + // TODO : Fix remove mediator + //var result = await _mediator.Send(new StartBotCommand(botType, + // botName, + // ticker, + // "scenario", + // timeframe, + // accountName, + // true)); + + var result = "down"; + message += $"I tried to start the bot called {botName}, his status is now {result}. "; + + if (result == "Up") + { + await Context.Message.AddReactionAsync(new Emoji("👌")); + message += $"this bot gonna watch {ticker} on {timeframe} timeframe."; + } + else + { + await Context.Message.AddReactionAsync(new Emoji("😒")); + message += $"something wrong happen, the bot status is down"; + } + + await Context.Channel.SendMessageAsync(message); + } + + [Command("stop")] + public async Task Stop([Remainder] string rest) + { + Typing(); + var parameters = rest.Split(_separator, StringSplitOptions.None); + var botType = MiscExtensions.ParseEnum(parameters[0]); + var botName = parameters[1]; + var result = "down"; + //var result = await _mediator.Send(new StopBotCommand(botType, botName)); + + await Context.Channel.SendMessageAsync(_messagePrefix + $"the {botType} called {botName} is now {result}"); + } + + [Command("hi")] + public async Task Hi([Remainder] string rest) + { + Typing(); + await Context.Channel.SendMessageAsync("I don't understand yet : " + rest); + } + + [Command("help")] + public async Task Help([Remainder] string rest) + { + Typing(); + + var message = _messagePrefix; + switch (rest) + { + case "backtest": + message += "to run a backtest you should use this pattern : !trader backtest ticker exchange timeframe startFromDays"; + break; + case "run": + message += "to run a bot you should use this pattern : !trader run botType botName ticker timeframe"; + break; + case "stop": + message += "to stop a bot you should use this pattern : !trader stop botType botName"; + break; + case "botType": + message += $"the bot type available are : {BotType.FlippingBot}, {BotType.ScalpingBot}"; + break; + case "timeframe": + message += $"the bot can currently handle only those timeframes : " + + $"{Timeframe.FifteenMinutes}, {Timeframe.ThirtyMinutes}, {Timeframe.OneDay}"; + break; + case "risklevel": + message += $"the bot can currently handle only those riskLevel : " + + $"{RiskLevel.High}"; + break; + default: + message += "I don't no the command"; + break; + } + + await Context.Channel.SendMessageAsync(message); + } + + private async void Typing() + { + await Context.Channel.TriggerTypingAsync(); + } + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordHelpers.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordHelpers.cs new file mode 100644 index 0000000..bd2e298 --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordHelpers.cs @@ -0,0 +1,80 @@ +using Discord; +using Managing.Domain.Statistics; +using Managing.Domain.Trades; + +namespace Managing.Infrastructure.Messengers.Discord; + +public static class DiscordHelpers +{ + public static Embed GetTradersEmbed(List traders, string title) + { + var fields = new List(); + + traders = traders.OrderByDescending(t => t.Winrate).ToList(); + foreach (var trader in traders) + { + fields.Add(new EmbedFieldBuilder + { + Name = $"{GetExplorerUrl(trader.Address)}", + Value = $"Avg Win / Avg Loss / Winrate / ROI \n {trader.AverageWin:#.##}$ / {trader.AverageLoss:#.##}$ / {trader.Winrate}% / {Convert.ToDecimal(trader.Roi) * 100:#.##}%", + }); + } + + var embed = new EmbedBuilder + { + Author = new EmbedAuthorBuilder() { Name = "GMX" }, + Title = $"{title} {DateTime.UtcNow:d}", + Color = Color.Gold, + Fields = fields, + }.Build(); + + return embed; + } + + public static Embed GetEmbed(string address, string title, List fields, Color color) + { + return new EmbedBuilder + { + Author = new EmbedAuthorBuilder() { Name = address }, + Title = title, + Color = color, + Fields = fields, + Url = GetExplorerUrl(address) + }.Build(); + } + + private static string GetExplorerUrl(string key) + { + return $"https://www.tradao.xyz/#/user/{key}/1"; + } + + internal static Embed GetTradesEmbed(List trades, string title) + { + var fields = new List(); + + foreach (var trade in trades) + { + fields.Add(new EmbedFieldBuilder + { + Name = $"{GetExplorerUrl(trade.ExchangeOrderId)}", + Value = $"Side / Ticker / Open / Qty / Leverage / LiqPrice \n {trade.Direction} / {trade.Ticker} / {trade.Price:#.##}$ / {trade.Quantity:#.##}$ / x{trade.Leverage:#.##} / {Convert.ToDecimal(trade.Message):#.##}$", + }); + } + + fields.Add(new EmbedFieldBuilder + { + Name = "Summary", + Value = $"Long / Short / \n {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Long)} / {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Short)}" + }); + + var embed = new EmbedBuilder + { + Author = new EmbedAuthorBuilder() { Name = "GMX" }, + Title = $"{title} {DateTime.UtcNow:d}", + Color = Color.DarkBlue, + Fields = fields, + }.Build(); + + return embed; + } +} diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs new file mode 100644 index 0000000..2545dab --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs @@ -0,0 +1,557 @@ +using Discord; +using Discord.Commands; +using Discord.Net; +using Discord.WebSocket; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Trading; +using Managing.Application.Trading.Commands; +using Managing.Application.Workers.Abstractions; +using Managing.Common; +using Managing.Core; +using Managing.Domain.Statistics; +using Managing.Domain.Trades; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Messengers.Discord +{ + public class DiscordService : IHostedService, IDisposable, IDiscordService + { + private const string _separator = "|"; + private readonly DiscordSocketClient _client; + private readonly CommandService _commandService; + private readonly IServiceProvider _services; + private readonly IExchangeService _exchangeService; + private readonly IMoneyManagementService _moneyManagementService; + private readonly IAccountService _accountService; + private readonly ITradingService _tradingService; + private readonly IStatisticService _statisticService; + + private readonly DiscordSettings _settings; + private readonly ILogger _logger; + + private readonly string _explorerUrl = ""; + + public DiscordService(DiscordSocketClient client, + CommandService commandService, + IServiceProvider services, + DiscordSettings settings, ILogger logger) + { + _client = client; + _commandService = commandService; + _services = services; + _settings = settings; + _logger = logger; + } + + #region Setup + // The hosted service has started + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_settings.HandleUserAction) + { + _client.ButtonExecuted += ButtonHandler; + _commandService.CommandExecuted += CommandExecuted; + _client.SlashCommandExecuted += SlashCommandHandler; + } + + _client.Ready += ClientReady; + _client.Log += Log; + _commandService.Log += Log; + // look for classes implementing ModuleBase to load commands from + await _commandService.AddModulesAsync(GetType().Assembly, _services); + // log in to Discord, using the provided token + await _client.LoginAsync(TokenType.Bot, _settings.Token); + // start bot + await _client.StartAsync(); + } + + private async Task SlashCommandHandler(SocketSlashCommand command) + { + await command.DeferAsync(); + + // Let's add a switch statement for the command name so we can handle multiple commands in one event. + switch (command.Data.Name) + { + case Constants.DiscordSlashCommand.Leaderboard: + await SlashCommands.HandleLeaderboardCommand(_services, command); + break; + case Constants.DiscordSlashCommand.Noobiesboard: + await SlashCommands.HandleNoobiesboardCommand(_services, command); + break; + case Constants.DiscordSlashCommand.LeaderboardPosition: + await SlashCommands.HandleLeadboardPositionCommand(_services, command); + break; + + } + } + + private async Task ClientReady() + { + // set status to online + await _client.SetStatusAsync(UserStatus.Online); + // Discord started as a game chat service, so it has the option to show what games you are playing + // Here the bot will display "Playing dead" while listening + await _client.SetGameAsync(_settings.BotActivity, "https://moon.com", ActivityType.Playing); + + if (!_settings.HandleUserAction) return; + + List applicationCommandProperties = new(); + try + { + var leaderBoardCommand = new SlashCommandBuilder(); + leaderBoardCommand.WithName(Constants.DiscordSlashCommand.Leaderboard); + leaderBoardCommand.WithDescription("Shows the last leaderboard"); + applicationCommandProperties.Add(leaderBoardCommand.Build()); + + var leadboardPositionsCommand = new SlashCommandBuilder(); + leadboardPositionsCommand.WithName(Constants.DiscordSlashCommand.LeaderboardPosition); + leadboardPositionsCommand.WithDescription("Shows the opened position of the leaderboard"); + applicationCommandProperties.Add(leadboardPositionsCommand.Build()); + + var noobiesboardCommand = new SlashCommandBuilder(); + noobiesboardCommand.WithName(Constants.DiscordSlashCommand.Noobiesboard); + noobiesboardCommand.WithDescription("Shows the last Noobies board"); + applicationCommandProperties.Add(noobiesboardCommand.Build()); + + + await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray()); + } + catch (ApplicationCommandException exception) + { + var json = JsonConvert.SerializeObject(exception, Formatting.Indented); + Console.WriteLine(json); + } + } + + public static List GetSlashCommands() + { + List commands = new(); + + + + return commands; + } + + // logging + private async Task Log(LogMessage arg) + { + await Task.Run(() => + { + _logger.LogInformation(arg.ToString()); + }); + } + + private async Task CommandExecuted(Optional command, ICommandContext context, IResult result) + { + // if a command isn't found + if (!command.IsSpecified) + { + await context.Message.AddReactionAsync(new Emoji("🤨")); // eyebrow raised emoji + return; + } + + // log failure to the console + if (!result.IsSuccess) + { + await Log(new LogMessage(LogSeverity.Error, nameof(CommandExecuted), $"Error: {result.ErrorReason}")); + return; + } + // react to message + await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji + } + + // the hosted service is stopping + public async Task StopAsync(CancellationToken cancellationToken) + { + await _client.SetGameAsync(null); + await _client.SetStatusAsync(UserStatus.Offline); + await _client.StopAsync(); + _client.Log -= Log; + _client.Ready -= ClientReady; + _commandService.Log -= Log; + _commandService.CommandExecuted -= CommandExecuted; + _client.ButtonExecuted -= ButtonHandler; + _client.SlashCommandExecuted -= SlashCommandHandler; + } + public void Dispose() + { + _client?.Dispose(); + } + #endregion + + #region In + public async Task ButtonHandler(SocketMessageComponent component) + { + var parameters = component.Data.CustomId.Split(new[] { _separator }, StringSplitOptions.None); + var command = parameters[0]; + + if (component.User.GlobalName != "crypto_saitama") + { + await component.Channel.SendMessageAsync("Sorry bro, this feature is not accessible for you.. Do not hesitate to send me approx. 456 121 $ and i give you full access"); + } + else + { + switch (command) + { + case Constants.DiscordButtonAction.OpenPosition: + await OpenPosition(component, parameters); + break; + case Constants.DiscordButtonAction.ClosePosition: + await ClosePosition(component, parameters); + break; + case Constants.DiscordButtonAction.CopyPosition: + await CopyPosition(component, parameters); + break; + default: + break; + } + } + } + + private async Task CopyPosition(SocketMessageComponent component, string[] parameters) + { + await component.Channel.SendMessageAsync("Let met few seconds to copy this position"); + await component.Channel.TriggerTypingAsync(); + + var json = MiscExtensions.Base64Decode(parameters[1]); + var trade = JsonConvert.DeserializeObject(json); + await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading, trade.Ticker, trade.Direction, Timeframe.FifteenMinutes, DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage); ; + } + + public async Task OpenPosition(SocketMessageComponent component, string[] parameters) + { + await component.Channel.SendMessageAsync("Let met few seconds to open a position"); + await component.Channel.TriggerTypingAsync(); + + var accountName = MiscExtensions.ParseEnum(parameters[1]); + var ticker = MiscExtensions.ParseEnum(parameters[2]); + var direction = MiscExtensions.ParseEnum(parameters[3]); + var timeframe = MiscExtensions.ParseEnum(parameters[4]); + var moneyManagementName = parameters[5]; + var expiration = DateTime.Parse(parameters[6]); + + await OpenPosition(component, accountName, moneyManagementName, PositionInitiator.User, ticker, direction, timeframe, expiration, false); + } + + private async Task OpenPosition(SocketMessageComponent component, string accountName, string moneyManagement, PositionInitiator initiator, Ticker ticker, TradeDirection direction, Timeframe timeframe, DateTime expiration, bool ignoreSLTP, decimal? leverage = null) + { + if (DateTime.Now > expiration) + { + await component.Channel.SendMessageAsync("Sorry I can't open position because you tried to click on a expired button."); + } + else + { + var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService)); + var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService)); + var accountService = (IAccountService)_services.GetService(typeof(IAccountService)); + var tradingService = (ITradingService)_services.GetService(typeof(ITradingService)); + + var tradeCommand = new OpenPositionRequest( + accountName, + await moneyManagementService.GetMoneyMangement(moneyManagement), + direction, + ticker, + initiator, + DateTime.UtcNow, + ignoreSLTP: ignoreSLTP); + var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(tradeCommand); + + var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}"); + + await component.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position), + components: builder.Build()); + } + } + + + private string GetClosingPositionMessage(Position position) + { + return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" + + $"Open Price : {position.Open.Price} \n" + + $"Closing Price : {position.Open.Price} \n" + + $"Quantity :{position.Open.Quantity} \n" + + $"PNL : {position.ProfitAndLoss.Net} $"; + } + + private async Task ClosePosition(SocketMessageComponent component, string[] parameters) + { + var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService)); + var accountService = (IAccountService)_services.GetService(typeof(IAccountService)); + var tradingService = (ITradingService)_services.GetService(typeof(ITradingService)); + + await component.RespondAsync("Alright, let met few seconds to close this position"); + var position = _tradingService.GetPositionByIdentifier(parameters[1]); + var command = new ClosePositionCommand(position); + var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command); + var fields = new List() + { + new EmbedFieldBuilder + { + Name = "Direction", + Value = position.OriginDirection, + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Open Price", + Value = $"{position.Open.Price:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Quantity", + Value = $"{position.Open.Quantity:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Pnl", + Value = $"{position.ProfitAndLoss.Net:#.##}", + IsInline = true + }, + }; + var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields, position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red); + await component.Channel.SendMessageAsync("", embed: embed); + } + + #endregion + + + #region Out + public async Task SendSignal(string message) + { + var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel; + var builder = new ComponentBuilder().WithButton("Open Position", $"openposition{_separator}"); + await channel.SendMessageAsync(message, components: builder.Build()); + } + + public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe) + { + var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G"); + var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel; + var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}"); + await channel.SendMessageAsync(message, components: builder.Build()); + } + + public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null) + { + var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel; + + + var fields = new List() + { + new EmbedFieldBuilder + { + Name = "Last size update", + Value = $"{trade.Date:s}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Entry Price", + Value = $"{trade.Price:#.##} $", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Quantity", + Value = $"{trade.Quantity / trade.Leverage:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Leverage", + Value = $"x{trade.Leverage:#.##}", + IsInline = true + } + }; + + if (oldTrade != null) + { + fields.Add(new EmbedFieldBuilder { Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $" }); + } + + var titlePrefix = oldTrade != null ? "Increase " : ""; + + var builder = new ComponentBuilder(); + + var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService)); + var moneyManagements = moneyManagementService.GetMoneyMangements(); + + foreach (var mm in moneyManagements) + { + var data = new CopyTradeData + { + Direction = trade.Direction, + Ticker = trade.Ticker, + AccountName = copyAccountName, + ExpirationMinute = 10, + Leverage = trade.Leverage, + }; + data.MoneyManagementName = mm.Name; + var encodedData = MiscExtensions.Base64Encode(JsonConvert.SerializeObject(data)); + + if (oldTrade == null) + { + builder.WithButton($"Copy with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}"); + } + else + { + builder.WithButton($"Increase with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}"); + } + } + + + var embed = DiscordHelpers.GetEmbed(address, $"{titlePrefix}{trade.Direction} {trade.Ticker}", fields, trade.Direction == TradeDirection.Long ? Color.Green : Color.Red); + await channel.SendMessageAsync("", components: builder.Build(), embed: embed); + } + + public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe) + { + var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G"); + var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel; + var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}"); + await channel.SendMessageAsync(message, components: builder.Build()); + } + + public async Task SendMessage(string message) + { + var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel; + await channel.SendMessageAsync(message); + } + + public async Task SendClosingPosition(Position position) + { + var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel; + await channel.SendMessageAsync(GetClosingPositionMessage(position)); + } + + public async Task SendTradeMessage(string message, bool isBadBehavior = false) + { + var channel = _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as IMessageChannel; + await channel.SendMessageAsync(message); + } + + public async Task SendClosedPosition(string address, Trade oldTrade) + { + var fields = new List() + { + new EmbedFieldBuilder + { + Name = "Last size update", + Value = $"{oldTrade.Date:s}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Entry Price", + Value = $"{oldTrade.Price:#.##} $", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Quantity", + Value = $"{oldTrade.Quantity / oldTrade.Leverage:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Leverage", + Value = $"x{oldTrade.Leverage:#.##}", + IsInline = true + } + }; + + var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields, oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed); + var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel; + await channel.SendMessageAsync("", embed: embed); + } + + + public async Task SendDecreasePosition(string address, Trade trade, decimal decreaseAmount) + { + var fields = new List() + { + new EmbedFieldBuilder + { + Name = "Last size update", + Value = $"{trade.Date:s}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Entry Price", + Value = $"{trade.Price:#.##} $", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Quantity", + Value = $"{trade.Quantity / trade.Leverage:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Leverage", + Value = $"x{trade.Leverage:#.##}", + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Decrease amount", + Value = $"{decreaseAmount:#.##} $" + } + }; + + var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields, Color.Blue); + var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel; + await channel.SendMessageAsync("", embed: embed); + } + + + public async Task SendPosition(Position position) + { + var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel; + var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}"); + await channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position), components: builder.Build()); + } + + public async Task SendBestTraders(List traders) + { + var channel = _client.GetChannel(_settings.LeaderboardChannelId) as IMessageChannel; + await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard")); + + } + + public async Task SendBadTraders(List traders) + { + var channel = _client.GetChannel(_settings.NoobiesboardChannelId) as IMessageChannel; + await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard")); + } + + #endregion + + public class CopyTradeData + { + [JsonProperty(PropertyName = "D")] + public TradeDirection Direction { get; set; } + [JsonProperty(PropertyName = "T")] + public Ticker Ticker { get; set; } + [JsonProperty(PropertyName = "A")] + public string AccountName { get; set; } + [JsonProperty(PropertyName = "E")] + public int ExpirationMinute { get; set; } + [JsonProperty(PropertyName = "L")] + public decimal Leverage { get; set; } + [JsonProperty(PropertyName = "M")] + public string MoneyManagementName { get; internal set; } + } + + + } +} diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordSettings.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordSettings.cs new file mode 100644 index 0000000..3315998 --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordSettings.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Configuration; + +namespace Managing.Infrastructure.Messengers.Discord +{ + public class DiscordSettings + { + public DiscordSettings(IConfiguration config) + { + Token = config.GetValue("Discord:TokenId"); + SignalChannelId = config.GetValue("Discord:SignalChannelId"); + CopyTradingChannelId = config.GetValue("Discord:CopyTradingChannelId"); + TradesChannelId = config.GetValue("Discord:TradesChannelId"); + TroublesChannelId = config.GetValue("Discord:TroublesChannelId"); + RequestsChannelId = config.GetValue("Discord:RequestsChannelId"); + LeaderboardChannelId = config.GetValue("Discord:LeaderboardChannelId"); + NoobiesboardChannelId = config.GetValue("Discord:NoobiesboardChannelId"); + ButtonExpirationMinutes = config.GetValue("Discord:ButtonExpirationMinutes"); + HandleUserAction = config.GetValue("Discord:HandleUserAction"); + BotActivity = config.GetValue("Discord:BotActivity"); + BotEnabled = true; + } + + public int ButtonExpirationMinutes { get; set; } + public bool HandleUserAction { get; } + public string BotActivity { get; } + public string Token { get; } + public ulong SignalChannelId { get; } + public ulong CopyTradingChannelId { get; } + public ulong TradesChannelId { get; } + public ulong TroublesChannelId { get; } + public ulong RequestsChannelId { get; } + public bool BotEnabled { get; set; } + public ulong LeaderboardChannelId { get; set; } + public ulong NoobiesboardChannelId { get; set; } + + } +} diff --git a/src/Managing.Infrastructure.Messengers/Discord/MessengerHelpers.cs b/src/Managing.Infrastructure.Messengers/Discord/MessengerHelpers.cs new file mode 100644 index 0000000..dbc78de --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/MessengerHelpers.cs @@ -0,0 +1,15 @@ +using Managing.Domain.Trades; + +namespace Managing.Infrastructure.Messengers.Discord; + +public class MessengerHelpers +{ + public static string GetPositionMessage(Position position) + { + return $"Position : {position.OriginDirection} {position.Open.Ticker} \n" + + $"Open Price : {position.Open.Price} \n" + + $"Quantity : {position.Open.Quantity}. \n" + + $"SL : {position.StopLoss.Price} \n" + + $"TP : {position.TakeProfit1.Price}"; + } +} diff --git a/src/Managing.Infrastructure.Messengers/Discord/SlashCommands.cs b/src/Managing.Infrastructure.Messengers/Discord/SlashCommands.cs new file mode 100644 index 0000000..aab398e --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Discord/SlashCommands.cs @@ -0,0 +1,28 @@ +using Discord.WebSocket; +using Managing.Application.Workers.Abstractions; + +namespace Managing.Infrastructure.Messengers.Discord; + +public static class SlashCommands +{ + public static async Task HandleLeaderboardCommand(IServiceProvider service, SocketSlashCommand command) + { + var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService)); + var traders = statisticService.GetBestTraders(); + await command.FollowupAsync(embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"), ephemeral: true); + } + + public static async Task HandleNoobiesboardCommand(IServiceProvider service, SocketSlashCommand command) + { + var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService)); + var traders = statisticService.GetBadTraders(); + await command.FollowupAsync(embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"), ephemeral: true); + } + + public static async Task HandleLeadboardPositionCommand(IServiceProvider service, SocketSlashCommand command) + { + var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService)); + var trades = await statisticService.GetLeadboardPositons(); + await command.FollowupAsync(embed: DiscordHelpers.GetTradesEmbed(trades, "Leaderboard Open position"), ephemeral: true); + } +} diff --git a/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj b/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj new file mode 100644 index 0000000..2b99e86 --- /dev/null +++ b/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + + + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.MongoDb/Attributes/BsonCollectionAttribute.cs b/src/Managing.Infrastructure.MongoDb/Attributes/BsonCollectionAttribute.cs new file mode 100644 index 0000000..3a5b35d --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Attributes/BsonCollectionAttribute.cs @@ -0,0 +1,13 @@ +namespace Managing.Infrastructure.MongoDb.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class BsonCollectionAttribute : Attribute + { + public string CollectionName { get; } + + public BsonCollectionAttribute(string collectionName) + { + CollectionName = collectionName; + } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/AccountDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/AccountDto.cs new file mode 100644 index 0000000..6d82cde --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/AccountDto.cs @@ -0,0 +1,14 @@ +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; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.MongoDb/Collections/BacktestDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/BacktestDto.cs new file mode 100644 index 0000000..e0e6c81 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/BacktestDto.cs @@ -0,0 +1,24 @@ +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 Positions { get; set; } + public List Signals { get; set; } + public Timeframe Timeframe { get; set; } + public RiskLevel RiskLevel { get; set; } + public string AccountName { get; set; } + public List Candles { get; set; } + public BotType BotType { get; set; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/CandleDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/CandleDto.cs new file mode 100644 index 0000000..a0d8f03 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/CandleDto.cs @@ -0,0 +1,28 @@ +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; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/MoneyManagementDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/MoneyManagementDto.cs new file mode 100644 index 0000000..11bf506 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/MoneyManagementDto.cs @@ -0,0 +1,18 @@ +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; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/PositionDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/PositionDto.cs new file mode 100644 index 0000000..e6837f8 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/PositionDto.cs @@ -0,0 +1,24 @@ +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; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/ScenarioDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/ScenarioDto.cs new file mode 100644 index 0000000..fa51ba7 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/ScenarioDto.cs @@ -0,0 +1,12 @@ +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 Strategies { get; set; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/SignalDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/SignalDto.cs new file mode 100644 index 0000000..ba20b5f --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/SignalDto.cs @@ -0,0 +1,20 @@ +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; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/StrategyDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/StrategyDto.cs new file mode 100644 index 0000000..f438eb0 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/StrategyDto.cs @@ -0,0 +1,21 @@ +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 Timeframe Timeframe { 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; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/TopVolumeTickerDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/TopVolumeTickerDto.cs new file mode 100644 index 0000000..20813f0 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/TopVolumeTickerDto.cs @@ -0,0 +1,15 @@ +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; } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/TradeDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/TradeDto.cs new file mode 100644 index 0000000..8e6afa0 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/TradeDto.cs @@ -0,0 +1,24 @@ +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("Trades")] + public class TradeDto : Document + { + [BsonDateTimeOptions] + public DateTime Date { get; set; } + public TradeDirection Direction { get; set; } + public TradeStatus Status { get; set; } + public TradeType TradeType { get; set; } + public string Ticker { get; set; } + public decimal Fee { get; set; } + public decimal Quantity { get; set; } + public decimal Price { get; set; } + public decimal Leverage { get; set; } + public string ExchangeOrderId { get; set; } + public string Message { get; set; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Collections/WorkerDto.cs b/src/Managing.Infrastructure.MongoDb/Collections/WorkerDto.cs new file mode 100644 index 0000000..7abcc36 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Collections/WorkerDto.cs @@ -0,0 +1,15 @@ +using Managing.Infrastructure.MongoDb.Attributes; +using Managing.Infrastructure.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.MongoDb.Collections; + +[BsonCollection("Workers")] +public class WorkerDto : Document +{ + public WorkerType WorkerType { get; set; } + public DateTime StartTime { get; set; } + public DateTime? LastRunTime { get; set; } + public int ExecutionCount { get; set; } + public TimeSpan Delay { get; set; } +} diff --git a/src/Managing.Infrastructure.MongoDb/Configurations/Document.cs b/src/Managing.Infrastructure.MongoDb/Configurations/Document.cs new file mode 100644 index 0000000..c0e04dd --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Configurations/Document.cs @@ -0,0 +1,13 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Managing.Infrastructure.MongoDb.Configurations +{ + public abstract class Document : IDocument + { + [BsonId] + public ObjectId Id { get; set; } + [BsonDateTimeOptions] + public DateTime CreatedAt => Id.CreationTime; + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Configurations/IDocument.cs b/src/Managing.Infrastructure.MongoDb/Configurations/IDocument.cs new file mode 100644 index 0000000..85e74b3 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Configurations/IDocument.cs @@ -0,0 +1,14 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Managing.Infrastructure.MongoDb.Configurations +{ + public interface IDocument + { + [BsonId] + [BsonRepresentation(BsonType.String)] + ObjectId Id { get; set; } + + DateTime CreatedAt { get; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Configurations/IManagingDatabaseSettings.cs b/src/Managing.Infrastructure.MongoDb/Configurations/IManagingDatabaseSettings.cs new file mode 100644 index 0000000..9d57568 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Configurations/IManagingDatabaseSettings.cs @@ -0,0 +1,8 @@ +namespace Managing.Infrastructure.MongoDb +{ + public interface IManagingDatabaseSettings + { + string ConnectionString { get; set; } + string DatabaseName { get; set; } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Configurations/ManagingDatabaseSettings.cs b/src/Managing.Infrastructure.MongoDb/Configurations/ManagingDatabaseSettings.cs new file mode 100644 index 0000000..9235ffb --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Configurations/ManagingDatabaseSettings.cs @@ -0,0 +1,7 @@ +namespace Managing.Infrastructure.MongoDb; + +public class ManagingDatabaseSettings : IManagingDatabaseSettings +{ + public string ConnectionString { get; set; } + public string DatabaseName { get; set; } +} diff --git a/src/Managing.Infrastructure.MongoDb/IMongoRepository.cs b/src/Managing.Infrastructure.MongoDb/IMongoRepository.cs new file mode 100644 index 0000000..93a8836 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/IMongoRepository.cs @@ -0,0 +1,55 @@ +using Managing.Infrastructure.MongoDb.Configurations; +using System.Linq.Expressions; + +namespace Managing.Infrastructure.MongoDb +{ + public interface IMongoRepository where TDocument : IDocument + { + IQueryable AsQueryable(); + + IEnumerable FilterBy( + Expression> filterExpression); + + IEnumerable FindAll(); + + IEnumerable FilterBy( + Expression> filterExpression, + Expression> projectionExpression); + + TDocument FindOne(Expression> filterExpression); + + Task FindOneAsync(Expression> filterExpression); + + TDocument FindById(string id); + + Task FindByIdAsync(string id); + + void InsertOne(TDocument document); + + Task InsertOneAsync(TDocument document); + + void InsertMany(ICollection documents); + + Task InsertManyAsync(ICollection documents); + + void ReplaceOne(TDocument document); + + Task ReplaceOneAsync(TDocument document); + + void DeleteOne(Expression> filterExpression); + + Task DeleteOneAsync(Expression> filterExpression); + + void DeleteById(string id); + + Task DeleteByIdAsync(string id); + + void DeleteMany(Expression> filterExpression); + + Task DeleteManyAsync(Expression> filterExpression); + + void Update(TDocument entity); + void CreateIndex(string column); + void DropCollection(); + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj b/src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj new file mode 100644 index 0000000..821412a --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + AnyCPU;x64 + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.MongoDb/MongoHelpers.cs b/src/Managing.Infrastructure.MongoDb/MongoHelpers.cs new file mode 100644 index 0000000..0dbf2c0 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/MongoHelpers.cs @@ -0,0 +1,20 @@ +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Managing.Infrastructure.MongoDb +{ + public static class MongoHelpers + { + public static async Task EnsureIndexExists(this IMongoDatabase database, string collectionName, string indexName) + { + var collection = database.GetCollection(collectionName); + var index = new BsonDocument + { + {indexName, 1} + }; + + var indexModel = new CreateIndexModel(index, new CreateIndexOptions { Unique = true }); + await collection.Indexes.CreateOneAsync(indexModel).ConfigureAwait(false); + } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/MongoRepository.cs b/src/Managing.Infrastructure.MongoDb/MongoRepository.cs new file mode 100644 index 0000000..9ac0086 --- /dev/null +++ b/src/Managing.Infrastructure.MongoDb/MongoRepository.cs @@ -0,0 +1,173 @@ +using Managing.Infrastructure.MongoDb.Attributes; +using Managing.Infrastructure.MongoDb.Configurations; +using MongoDB.Bson; +using MongoDB.Driver; +using System.Linq.Expressions; + +namespace Managing.Infrastructure.MongoDb +{ + public class MongoRepository : IMongoRepository + where TDocument : IDocument + { + private readonly IMongoCollection _collection; + private readonly IMongoDatabase _database; + + public MongoRepository(IManagingDatabaseSettings settings) + { + _database = new MongoClient(settings.ConnectionString).GetDatabase(settings.DatabaseName); + _collection = _database.GetCollection(GetCollectionName(typeof(TDocument))); + } + + private protected string GetCollectionName(Type documentType) + { + return ((BsonCollectionAttribute)documentType.GetCustomAttributes( + typeof(BsonCollectionAttribute), + true) + .FirstOrDefault())?.CollectionName; + } + + public virtual IQueryable AsQueryable() + { + return _collection.AsQueryable(); + } + + public virtual IEnumerable FilterBy( + Expression> filterExpression) + { + return _collection.Find(filterExpression).ToEnumerable(); + } + + public virtual IEnumerable FindAll() + { + return _collection.Find(_ => true).ToEnumerable(); + } + + public virtual IEnumerable FilterBy( + Expression> filterExpression, + Expression> projectionExpression) + { + return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable(); + } + + public virtual TDocument FindOne(Expression> filterExpression) + { + return _collection.Find(filterExpression).FirstOrDefault(); + } + + public virtual Task FindOneAsync(Expression> filterExpression) + { + return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync()); + } + + public virtual TDocument FindById(string id) + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + return _collection.Find(filter).SingleOrDefault(); + } + + public virtual Task FindByIdAsync(string id) + { + return Task.Run(() => + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + return _collection.Find(filter).SingleOrDefaultAsync(); + }); + } + + + public virtual void InsertOne(TDocument document) + { + _collection.InsertOne(document); + } + + public virtual Task InsertOneAsync(TDocument document) + { + return Task.Run(() => _collection.InsertOneAsync(document)); + } + + public void InsertMany(ICollection documents) + { + _collection.InsertMany(documents); + } + + public void DropCollection() + { + _database.DropCollection(GetCollectionName(typeof(TDocument))); + } + + public virtual async Task InsertManyAsync(ICollection documents) + { + await _collection.InsertManyAsync(documents); + } + + public void ReplaceOne(TDocument document) + { + var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); + _collection.FindOneAndReplace(filter, document); + } + + public virtual async Task ReplaceOneAsync(TDocument document) + { + var filter = Builders.Filter.Eq(doc => doc.Id, document.Id); + await _collection.FindOneAndReplaceAsync(filter, document); + } + + public void Update(TDocument entity) + { + if (entity.Id == ObjectId.Empty) + { + entity.Id = ObjectId.GenerateNewId(); + } + + var option = new ReplaceOptions { IsUpsert = true }; + _collection.ReplaceOne(x => entity != null && x.Id == entity.Id, entity, option); + } + + public void DeleteOne(Expression> filterExpression) + { + _collection.FindOneAndDelete(filterExpression); + } + + public Task DeleteOneAsync(Expression> filterExpression) + { + return Task.Run(() => _collection.FindOneAndDeleteAsync(filterExpression)); + } + + public void DeleteById(string id) + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + _collection.FindOneAndDelete(filter); + } + + public Task DeleteByIdAsync(string id) + { + return Task.Run(() => + { + var objectId = new ObjectId(id); + var filter = Builders.Filter.Eq(doc => doc.Id, objectId); + _collection.FindOneAndDeleteAsync(filter); + }); + } + + public void DeleteMany(Expression> filterExpression) + { + _collection.DeleteMany(filterExpression); + } + + public Task DeleteManyAsync(Expression> filterExpression) + { + return _collection.DeleteManyAsync(filterExpression); + } + + public virtual void CreateIndex(string column) + { + var keys = Builders.IndexKeys.Ascending(column); + var indexOptions = new CreateIndexOptions { Unique = true }; + var model = new CreateIndexModel(keys, indexOptions); + _collection.Indexes.CreateOne(model); + } + } +} diff --git a/src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz b/src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/MoneyManagement.bson.gz new file mode 100644 index 0000000000000000000000000000000000000000..cb79d135f484f18ef201601266365467f0447a8f GIT binary patch literal 388 zcmV-~0ek)*iwFP!00000|2oLPz`!1#nZl6tH#m(;#Nz6-5bu0migH;&l z8JIFK1()O(_~aKCGl)Wj42_KqkbxlsQ%GWVYCutbT4o7MpAiF7U}<7rNoGkUk|YxY z1A`$06Ufq{#Pn1sp8?qe`A`o`NAkdomxMgPGETxL;Ys&Q`M8AM9cOdv?7VjEGy++DKn z`n`(fwdF-tP*sC0k-xlx?iC+U#X-s(_=n82*e+|;xpkm<|}*zMs8NG;0DPhk*nODrydu!|XZKzf67^2=cYQec71 z^t{9zm=Gt#vKw$eaz?I{@p!oMpr8B#Bu8o3wB14Yk%NJOA*eXhC9})~#fK9iKI|mP zhYYxUh-`}h)PF1t3=CYze&j;-9~Y|M(EPOy>aT@JPTAm(-Ct&O@s}CgUu-1$>j9F# zwnP232+1ioije&UN(e5QWzP9|X_-a2sBtzG;3c@fD#qqm5TxQew(Iz)=;{`;Db*2!cQ*AqVn00960 LqRb)?Fs`J|El8sL|or_WvOHxxDOBfWM zr(SPjWMB{osVqokU}6BVGILYYiV|~E8JHOu7?}J(EG`BH2H(Ww6z8J+;$jA_fYhSQ z{1gTOx5VNS2)mep2c$PRC%+sfAO#l4OwUWqfeCRkFfcH5!kxq!87|}TaOFWi`8h~V zy79Z^4#G(cxSYhnz`zhxoavHTX2u`@br2f^0|OVbqqvYA#D(e@6o({29WocmAup6# zNppw^+#xI^IiwfKA&pRn%tLm_d}N0(;&KQm23#`Bob&V2GK+Gl;h0{iW9B0{=EONN V97Apt0RjL3|NlCuqA9-u001HCay$S4 literal 0 HcmV?d00001 diff --git a/src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz b/src/Managing.Infrastructure.MongoDb/Seeds/ManagingDb/Strategies.metadata.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..743fc3131688f8785c5fbc838c15c90c615f99d4 GIT binary patch literal 133 zcmV;00DAu)iwFP!00000|76NR3c@fD1<<>ETxMZILfW~3XAmh(GD46}#WtdZxw~ZT z`n`tjd6Z3dP*Z~9Od nNQ1ivm(S7L=)4=p$=ei`DMXvH9Rl(string key) + { + var cachedData = distributedCache.GetString(key); + + if (!string.IsNullOrEmpty(cachedData)) + { + return JsonConvert.DeserializeObject(cachedData); + } + + return default(T); + } + + public void RemoveValue(string key) + { + distributedCache.Remove(key); + } + + public string SaveValue(string name, string value) + { + distributedCache.SetString(name, value); + return name; + } + + public void SaveValue(string name, T value, TimeSpan slidingExpiration) + { + var options = new DistributedCacheEntryOptions() + { + SlidingExpiration = slidingExpiration + }; + + distributedCache.SetString(name, JsonConvert.SerializeObject(value), options); + } + + public T GetOrSave(string name, Func action, TimeSpan slidingExpiration) + { + var cachedData = distributedCache.GetString(name); + + if (!string.IsNullOrEmpty(cachedData)) + { + return JsonConvert.DeserializeObject(cachedData); + } + + var options = new DistributedCacheEntryOptions() + { + SlidingExpiration = slidingExpiration + }; + + var result = action(); + + distributedCache.SetString(name, JsonConvert.SerializeObject(result), options); + return result; + } + } +} diff --git a/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj b/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj new file mode 100644 index 0000000..6d28033 --- /dev/null +++ b/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.Storage/TaskCache.cs b/src/Managing.Infrastructure.Storage/TaskCache.cs new file mode 100644 index 0000000..b89e52e --- /dev/null +++ b/src/Managing.Infrastructure.Storage/TaskCache.cs @@ -0,0 +1,82 @@ +using Managing.Application.Abstractions; +using Managing.Core; +//using Microsoft.Extensions.Caching.Memory; +using System.Runtime.Caching; + +namespace Managing.Infrastructure.Storage +{ + public class TaskCache : ITaskCache + { + private MemoryCache _cache { get; } = MemoryCache.Default; + private CacheItemPolicy _defaultPolicy { get; } = new CacheItemPolicy(); + + public async Task AddOrGetExisting(string key, Func> valueFactory) + { + + var asyncLazyValue = new AsyncLazy(valueFactory); + var existingValue = (AsyncLazy)_cache.AddOrGetExisting(key, asyncLazyValue, _defaultPolicy); + + if (existingValue != null) + { + asyncLazyValue = existingValue; + } + + try + { + var result = await asyncLazyValue; + + // The awaited Task has completed. Check that the task still is the same version + // that the cache returns (i.e. the awaited task has not been invalidated during the await). + if (asyncLazyValue != _cache.AddOrGetExisting(key, new AsyncLazy(valueFactory), _defaultPolicy)) + { + // The awaited value is no more the most recent one. + // Get the most recent value with a recursive call. + return await AddOrGetExisting(key, valueFactory); + } + return result; + } + catch (Exception) + { + // Task object for the given key failed with exception. Remove the task from the cache. + _cache.Remove(key); + // Re throw the exception to be handled by the caller. + throw; + } + } + + public void Invalidate(string key) + { + _cache.Remove(key); + } + + public bool Contains(string key) + { + return _cache.Contains(key); + } + + public void Clear() + { + // A snapshot of keys is taken to avoid enumerating collection during changes. + var keys = _cache.Select(i => i.Key).ToList(); + keys.ForEach(k => _cache.Remove(k)); + } + + public T Get(string key) + { + var existingValue = (AsyncLazy)_cache.Get(key); + return existingValue.Value.Result; + } + + public virtual List GetCache() + { + List list = new List(); + + foreach (var item in _cache) + { + list.Add((T)item.Value); + } + + return list; + } + } +} diff --git a/src/Managing.Infrastructure.Tests/EvmManagerTests.cs b/src/Managing.Infrastructure.Tests/EvmManagerTests.cs new file mode 100644 index 0000000..12902ee --- /dev/null +++ b/src/Managing.Infrastructure.Tests/EvmManagerTests.cs @@ -0,0 +1,379 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Common; +using Managing.Domain.Accounts; +using Managing.Domain.Evm; +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Infrastructure.Evm.Models.Gmx; +using Managing.Infrastructure.Evm.Referentials; +using Managing.Infrastructure.Evm.Services; +using Managing.Infrastructure.Evm.Services.Gmx; +using Nethereum.Contracts; +using Nethereum.Contracts.Standards.ERC721.ContractDefinition; +using Nethereum.Web3; +using System.Numerics; +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Tests; + + +public class EvmManagerTests +{ + private readonly IEvmManager _manager; + private readonly List _chains; + + public List Subgraphs; + public readonly string PublicAddress = ""; + + + public EvmManagerTests() + { + _manager = new EvmManager(Subgraphs); + _chains = ChainService.GetChains(); + } + + [Fact] + public void Should_construct_manager() + { + var manager = new EvmManager(Subgraphs); + Assert.IsType(manager); + } + + [Theory] + [InlineData("")] + public async void Should_get_address_balance(string address) + { + var balance = await _manager.GetAddressBalance(address); + Assert.IsType(balance); + Assert.True(balance > -1); + } + + // Connect to nft contract + [Theory] + [InlineData("0x17f4BAa9D35Ee54fFbCb2608e20786473c7aa49f")] + public async void Should_return_holder_list_for_nft_collection(string contract) + { + var holders = await _manager.GetContractHolders(contract, DateTime.UtcNow.AddDays(-30)); + Assert.IsType>(holders); + Assert.True(holders.Any()); + } + + [Theory] + [InlineData("0xa435530d50d7D17Fd9fc6E1c897Dbf7C08E12d35", "0x17f4BAa9D35Ee54fFbCb2608e20786473c7aa49f")] + public async void Should_return_event_transfer_nft(string owner, string contract) + { + var manager = new EvmManager(Subgraphs); + var holders = await manager.GetNftEvent(owner, contract); + Assert.IsType>>(holders); + Assert.True(holders.Any()); + } + + [Fact] + public async void Should_return_date_of_block() + { + var manager = new EvmManager(Subgraphs); + var date = await manager.GetBlockDate(38793245); + Assert.Equal(new DateTime(2022, 11, 17, 11, 15, 33), date); + } + + [Fact] + public void Should_verify_message_sign() + { + var message = "Message to sign"; + var address = "0x94618601FE6cb8912b274E5a00453949A57f8C1e"; + var privateKey = "0x7580e7fb49df1c861f0050fae31c2224c6aba908e116b8da44ee8cd927b990b0"; + + var manager = new EvmManager(Subgraphs); + + var signature = manager.SignMessage(message, privateKey); + var addressRecovered = manager.VerifySignature(signature, message); + + Assert.Equal(addressRecovered, address); + } + + [Fact] + public void Shoud_return_generated_evm_address() + { + var manager = new EvmManager(Subgraphs); + var keys = manager.GenerateAddress(); + + Assert.IsType<(string Key, string Secret)>(keys); + Assert.False(string.IsNullOrEmpty(keys.Key)); + Assert.False(string.IsNullOrEmpty(keys.Secret)); + } + + [Fact] + public void Should_return_correct_account_for_mnemo() + { + var mnemo = "twist enemy flame exchange summer roast beyond friend image pyramid topple need"; + var manager = new EvmManager(Subgraphs); + var publicAddress = "0x3aBAD913A70554f416944F1a4C0EAbF3BCAFB959"; + var address = manager.GetAddressFromMnemo(mnemo); + Assert.NotNull(address); + Assert.IsType(address); + Assert.Equal(publicAddress, address); + } + + [Theory] + //[InlineData("0x0425dEAb364E9121F7CA284129dA854FD5cF22eD", Constants.Chains.Ethereum)] + [InlineData("0x7002AE0Bae7fC67416230F025A32EfE086C0934E", Constants.Chains.Arbitrum)] + public async void Should_return_balances(string publicAddress, string chainName) + { + var manager = new EvmManager(Subgraphs); + var chain = _chains.First(c => c.Name == chainName); + var balances = await manager.GetBalances(chain, 0, 30, publicAddress); + + Assert.IsType>(balances); + Assert.NotEmpty(balances); + } + + [Theory] + //[InlineData("0x7002ae0bae7fc67416230f025a32efe086c0934e", Constants.Chains.Arbitrum)] + [InlineData("0xc62F5499789b716Aa94a421A60c76c8c13A31ab6", Constants.Chains.Ethereum)] + public async void Should_return_all_balance(string publicAddress, string chainName) + { + var manager = new EvmManager(Subgraphs); + var chain = _chains.First(c => c.Name == chainName); + var balances = await manager.GetAllBalances(chain, publicAddress); + + Assert.IsType>(balances); + Assert.True(balances.Count > 1); + } + + [Theory] + [InlineData("", Constants.Chains.Arbitrum, Ticker.GMX)] + public async void Should_return_token_balance(string publicAddress, string chainName, Ticker ticker) + { + var manager = new EvmManager(Subgraphs); + var balance = await manager.GetTokenBalance(chainName, ticker, publicAddress); + + Assert.IsType(balance); + Assert.True(balance.Balance > 0); + } + + [Theory] + [InlineData("")] + public async void Should_return_balance_of_ethers(string publicAddress) + { + var manager = new EvmManager(Subgraphs); + var chain = _chains.First(c => c.Name == Constants.Chains.Ethereum); + var balance = await manager.GetEtherBalance(chain, publicAddress); + + Assert.IsType(balance); + } + + [Theory] + [InlineData("")] + public async void Should_return_all_balance_for_all_chain(string publicAddress) + { + var manager = new EvmManager(Subgraphs); + var balances = await manager.GetAllBalancesOnAllChain(publicAddress); + + Assert.IsType>(balances); + Assert.True(balances.Count > 0); + } + + [Theory] + [InlineData(Ticker.BTC, Timeframe.FiveMinutes)] + public async void Get_Prices(Ticker ticker, Timeframe timeframe) + { + var manager = new EvmManager(Subgraphs); + var candles = await manager.GetCandles(SubgraphProvider.ChainlinkPrice, ticker, DateTime.UtcNow, timeframe); + + if (candles == null || !candles.Any()) + { + candles = await manager.GetCandles(SubgraphProvider.ChainlinkGmx, ticker, DateTime.UtcNow, timeframe); + } + Assert.NotNull(candles); + Assert.True(candles.Any()); + } + + [Fact] + public async void Get_Available_Tickers() + { + var manager = new EvmManager(Subgraphs); + var tickers = await manager.GetAvailableTicker(); + + Assert.NotEmpty(tickers); + } + + [Fact] + public async void GetLastCandle() + { + var manager = new EvmManager(Subgraphs); + var candle = await manager.GetCandle(SubgraphProvider.Gbc, Ticker.BTC); + + Assert.NotNull(candle); + } + + [Fact] + public async void Should_Init_Address_For_Trading() + { + var manager = new EvmManager(Subgraphs); + var accountInitilized = await manager.InitAddress(Constants.Chains.Arbitrum, PublicAddress, "PrivateKey"); + + Assert.True(accountInitilized); + } + + [Fact] + public async void Should_send_eth_from_account() + { + var manager = new EvmManager(Subgraphs); + var chain = _chains.First(c => c.Name == Constants.Chains.Arbitrum); + var balance = await manager.GetEtherBalance(chain, PublicAddress); + // Update receiver + var receiverAddress = ""; + var sendResult = await manager.Send( + chain, + Ticker.ETH, + balance.Balance / 2, + PublicAddress, + "", + receiverAddress); + + Assert.True(sendResult); + } + + [Fact] + public async void Should_send_Gmx_from_account() + { + var manager = new EvmManager(Subgraphs); + var chain = _chains.First(c => c.Name == Constants.Chains.Arbitrum); + var receiverAddress = ""; + var balance = await manager.GetTokenBalance(chain.Name, Ticker.GMX, PublicAddress); + + var sendResult = await manager.Send( + chain, + Ticker.GMX, + balance.Balance / 2, + PublicAddress, + "", + receiverAddress); + + Assert.True(sendResult); + } + + [Fact] + public async void Should_return_indexes_from_gmx() + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var indexes = await GmxService.GetLastIndex(web3, ""); + + Assert.IsType(indexes); + } + + [Theory] + [InlineData("")] + public async void Should_return_gmx_orders(string publicAddress) + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var orders = await GmxService.GetOrders(web3, publicAddress, Ticker.BTC); + + Assert.IsType>(orders); + } + + [Fact] + public async void Should_return_orders() + { + var manager = new EvmManager(Subgraphs); + var account = GetAccount(); + + var orders = await manager.GetOrders(account, Ticker.BTC); + + Assert.IsType>(orders); + } + + [Fact] + public async void Should_cancel_gmx_orders() + { + var manager = new EvmManager(Subgraphs); + var account = GetAccount(); + + var cancelled = await manager.CancelOrders(account, Ticker.BTC); + + Assert.IsType(cancelled); + } + + private static Account GetAccount() + { + return new Account + { + Key = "PublicAddress", + Secret = "PrivateKey" + }; + } + + [Fact] + public void Should_convert_quantity() + { + var quantity = Web3.Convert.ToWei(0.0019); + + Assert.IsType(quantity); + } + + [Fact] + public async void Should_approve_order() + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var approval = await GmxService.ApproveOrder(web3, Ticker.BTC, PublicAddress, 0.0003m); + + Assert.IsType(approval); + } + + [Fact] + public async void Should_check_approved_gmx_plugin() + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var isPluginAdded = await GmxService.IsPluginAdded(web3, "", Arbitrum.Address.OrderBook); + + Assert.IsType(isPluginAdded); + Assert.True(isPluginAdded); + } + + [Fact] + public void Should_return_correct_acceptable_price() + { + var acceptablePrice = GmxHelpers.GetAcceptablePrice(16672.76m, true); + var price = new BigInteger(1662274172); + var expected = Web3.Convert.ToWei(price, 25); + + Assert.NotNull(acceptablePrice); + Assert.IsType(acceptablePrice); + Assert.Equal(expected, acceptablePrice); + } + + [Fact] + public async void Should_return_quantity_in_position() + { + var manager = new EvmManager(Subgraphs); + var quantity = await manager.QuantityInPosition(Constants.Chains.Arbitrum, PublicAddress, Ticker.BTC); + + Assert.NotNull(quantity); + } + + [Fact] + public async void Should_return_Gmx_position() + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var position = await GmxService.GetGmxPosition(web3, "", Ticker.BTC); + + Assert.IsType(position); + } + + [Fact] + public async void Should_return_Trade() + { + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(chain.RpcUrl); + var position = await GmxService.GetTrade(web3, "", Ticker.ETH); + + Assert.IsType(position); + } +} diff --git a/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs b/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs new file mode 100644 index 0000000..7a6bdfa --- /dev/null +++ b/src/Managing.Infrastructure.Tests/ExchangeServicesTests.cs @@ -0,0 +1,200 @@ +using Managing.Common; +using Managing.Domain.Trades; +using Managing.Infrastructure.Exchanges; +using Microsoft.Extensions.Logging; +using Managing.Domain.Candles; +using Xunit; +using static Managing.Common.Enums; +using Managing.Domain.Accounts; +using Moq; +using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions.Services; +using Managing.Infrastructure.Exchanges.Abstractions; +using Managing.Infrastructure.Exchanges.Exchanges; +using Managing.Infrastructure.Evm; +using Ticker = Managing.Common.Enums.Ticker; +using Managing.Infrastructure.Evm.Abstractions; + +namespace Managing.Infrastructure.Tests +{ + public class ExchangeServicesTests + { + private readonly IExchangeService _exchangeService; + public readonly string PublicAddress = ""; + + public List Subgraphs; + + public ExchangeServicesTests() + { + + ILoggerFactory doesntDoMuch = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory(); + var candleRepository = new Mock().Object; + var evmManager = new EvmManager(Subgraphs); + var evmProcessor = new EvmProcessor(new Mock>().Object, evmManager); + var exchangeProcessors = new List() + { + evmProcessor + }; + + _exchangeService = new ExchangeService(doesntDoMuch.CreateLogger(), candleRepository, exchangeProcessors); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC)] + public void Should_Return_Price_For_Given_Ticker(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var price = _exchangeService.GetPrice(account, ticker, DateTime.Now); + Assert.IsType(price); + Assert.InRange(price, 0, 1000000); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.ADA)] + public void Should_Return_Candle_For_Given_Ticker(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var candle = _exchangeService.GetCandle(account, ticker, DateTime.Now); + Assert.IsType(candle); + Assert.InRange(candle.High, 0, 1000000); + Assert.InRange(candle.Low, 0, 1000000); + Assert.InRange(candle.Open, 0, 1000000); + Assert.InRange(candle.Close, 0, 1000000); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm)] + public void Should_Return_Balance(Enums.TradingExchanges exchange) + { + var account = GetAccount(exchange); + var balance = _exchangeService.GetBalance(account).Result; + Assert.IsType(balance); + Assert.True(balance >= 0); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, "0x2875673415c66bf05091eeff3887e0d40136d5ea443a4e63e7f4e41a6580575e", Ticker.BTC)] + public void Should_Return_Trade_For_Given_OrderId(Enums.TradingExchanges exchange, string orderId, Ticker ticker) + { + var account = GetAccount(exchange); + var trade = _exchangeService.GetTrade(account, orderId, ticker).Result; + Assert.IsType(trade); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC)] + public void Should_Return_List_Of_Candle_Given_Ticker(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-10), Timeframe.OneDay).Result; + Assert.IsType>(candles); + Assert.InRange(candles.Count, 1, 15); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.ADA, "7INRiu79cv2nCONNlILPu0")] + public void Should_Return_Long_Trade(Enums.TradingExchanges exchange, Ticker ticker, string exchangeOrderId) + { + var account = GetAccount(exchange); + var trade = _exchangeService.GetTrade(account, exchangeOrderId, ticker).Result; + Assert.IsType(trade); + Assert.True(trade.Direction == TradeDirection.Long); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.ADA, "AQKzJpDNrfVjuq81baPLfR")] + public void Should_Return_Short_Trade(Enums.TradingExchanges exchange, Ticker ticker, string exchangeOrderId) + { + var account = GetAccount(exchange); + var trade = _exchangeService.GetTrade(account, exchangeOrderId, ticker).Result; + Assert.IsType(trade); + Assert.True(trade.Direction == TradeDirection.Short); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC)] + public async void Should_Return_Balance_For_Ticker(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var balance = await _exchangeService.GetQuantityInPosition(account, ticker); + Assert.IsType(balance); + Assert.True(balance > 0); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.ADA)] + public void Should_Return_Trade_List_For_Ticker(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var trades = _exchangeService.GetTrades(account, ticker).Result; + Assert.IsType>(trades); + Assert.True(trades.Count > 0); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm)] + public void Should_Return_Fee(Enums.TradingExchanges exchange) + { + var account = GetAccount(exchange); + var fee = _exchangeService.GetFee(account); + Assert.IsType(fee); + Assert.True(fee > 0); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC)] + public void Should_Return_Volume(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var volume = _exchangeService.GetVolume(account, ticker); + Assert.IsType(volume); + Assert.True(volume > 0); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC)] + public async void Should_Return_Open_Order(Enums.TradingExchanges exchange, Ticker ticker) + { + var account = GetAccount(exchange); + var trades = await _exchangeService.GetOpenOrders(account, ticker); + Assert.IsType>(trades); + } + + [Theory] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC, 0.1, TradeDirection.Long)] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC, 700, TradeDirection.Long)] + [InlineData(Enums.TradingExchanges.Evm, Ticker.BTC, 700, TradeDirection.Short)] + public void Should_Return_Best_Price( + Enums.TradingExchanges exchange, + Ticker ticker, + decimal quantity, + TradeDirection direction) + { + var account = GetAccount(exchange); + var lastPrice = _exchangeService.GetPrice(account, ticker, DateTime.UtcNow); + var bestPrice = _exchangeService.GetBestPrice(account, ticker, lastPrice, quantity, direction); + + Assert.IsType(bestPrice); + + var percentageDiff = ( (bestPrice * 100) / lastPrice) - 100; + Assert.True(Math.Abs(percentageDiff) < 1); + } + + private Account GetAccount(Enums.TradingExchanges exchange) + { + var account = new Account(); + switch (exchange) + { + case Enums.TradingExchanges.Evm: + account.Exchange = Enums.TradingExchanges.Evm; + account.Key = PublicAddress; + account.Secret = "PrivateKey"; + account.Name = "EvmAccount"; + break; + default: + break; + } + return account; + } + } +} diff --git a/src/Managing.Infrastructure.Tests/Managing.Infrastructure.Tests.csproj b/src/Managing.Infrastructure.Tests/Managing.Infrastructure.Tests.csproj new file mode 100644 index 0000000..19517b9 --- /dev/null +++ b/src/Managing.Infrastructure.Tests/Managing.Infrastructure.Tests.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + AnyCPU;x64 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/Managing.Infrastructure.Tests/SubgraphTests.cs b/src/Managing.Infrastructure.Tests/SubgraphTests.cs new file mode 100644 index 0000000..ff2040d --- /dev/null +++ b/src/Managing.Infrastructure.Tests/SubgraphTests.cs @@ -0,0 +1,56 @@ +using Xunit; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Tests; + +public class SubgraphTests +{ + public SubgraphTests() + { + } + + // [Fact] + // public async void Should_get_price_from_chainlink_gmx() + // { + // var prices = await ChainlinkGmx.GetPrices(Ticker.BTC, DateTime.UtcNow.AddDays(-4), Timeframe.FiveMinutes); + + // Assert.NotNull(prices); + // Assert.True(prices.Any()); + // } + + // [Fact] + // public async void Should_get_price_from_gbc() + // { + // var prices = await GbcFeed.GetPrices(Ticker.BTC, DateTime.UtcNow.AddDays(-15), Timeframe.FiveMinutes); + + // Assert.NotNull(prices); + // Assert.True(prices.Any()); + // } + + // [Fact] + // public async void Should_get_price_from_chainlink() + // { + // var prices = await Chainlink.GetPrices(Ticker.BTC, DateTime.UtcNow.AddDays(-4), Timeframe.FiveMinutes); + + // Assert.NotNull(prices); + // Assert.True(prices.Any()); + // } + + // [Fact] + // public async void Should_get_top_tokens() + // { + // var top = await Uniswap.GetTopTokens(); + + // Assert.NotNull(top); + // Assert.True(top.Tokens.Any()); + // } + + // [Fact] + // public async void Should_get_available_pairs_for_chainlink() + // { + // var pairs = await Chainlink.GetTickers(); + + // Assert.NotNull(pairs); + // Assert.True(pairs.Any()); + // } +} diff --git a/src/Managing.Infrastructure.Tests/TradaoTests.cs b/src/Managing.Infrastructure.Tests/TradaoTests.cs new file mode 100644 index 0000000..b81ff8f --- /dev/null +++ b/src/Managing.Infrastructure.Tests/TradaoTests.cs @@ -0,0 +1,24 @@ +using Managing.Domain.Shared.Helpers; +using Managing.Infrastructure.Evm.Services; +using Xunit; + +namespace Managing.Infrastructure.Tests; + +public class TradaoTests +{ + [Fact] + public async void Should_return_best_trader() + { + var service = new TradaoService(); + var details = await service.GetBestTrader(); + Assert.NotNull(details); + } + + [Fact] + public async void Should_return_bad_trader() + { + var service = new TradaoService(); + var details = (await service.GetBadTrader()).FindBadTrader(); + Assert.NotNull(details); + } +} diff --git a/src/Managing.Infrastructure.Web3/Abstractions/ISubgraphPrices.cs b/src/Managing.Infrastructure.Web3/Abstractions/ISubgraphPrices.cs new file mode 100644 index 0000000..3cc1208 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Abstractions/ISubgraphPrices.cs @@ -0,0 +1,12 @@ +using Managing.Domain.Candles; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Abstractions; + +public interface ISubgraphPrices +{ + public SubgraphProvider GetProvider(); + Task> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe); + Task GetVolume(Ticker ticker); + Task> GetTickers(); +} diff --git a/src/Managing.Infrastructure.Web3/Abstractions/IUniswap.cs b/src/Managing.Infrastructure.Web3/Abstractions/IUniswap.cs new file mode 100644 index 0000000..2523915 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Abstractions/IUniswap.cs @@ -0,0 +1,9 @@ +using Managing.Infrastructure.Evm.Subgraphs.Models; + +namespace Managing.Infrastructure.Evm.Abstractions; + +public interface IUniswap : ISubgraphPrices +{ + Task GetMostLiquidMarketPairs(); + Task GetTopTokens(); +} diff --git a/src/Managing.Infrastructure.Web3/EvmManager.cs b/src/Managing.Infrastructure.Web3/EvmManager.cs new file mode 100644 index 0000000..2a7c6bd --- /dev/null +++ b/src/Managing.Infrastructure.Web3/EvmManager.cs @@ -0,0 +1,620 @@ +using Managing.Application.Abstractions.Repositories; +using Managing.Domain.Evm; +using NBitcoin; +using Nethereum.Contracts; +using Nethereum.Hex.HexTypes; +using Nethereum.Signer; +using Nethereum.Web3; +using Nethereum.HdWallet; +using System.Numerics; +using System.Net.Http.Json; +using Managing.Infrastructure.Evm.Services; +using Managing.Domain.Candles; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Core; +using static Managing.Common.Enums; +using Managing.Infrastructure.Evm.Services.Gmx; +using Nethereum.Contracts.Standards.ERC20.ContractDefinition; +using Managing.Common; +using Managing.Domain.Trades; +using Managing.Domain.Accounts; +using Managing.Infrastructure.Evm.Models.Gmx; +using Managing.Infrastructure.Evm.Referentials; + +namespace Managing.Infrastructure.Evm; + +public class EvmManager : IEvmManager +{ + private readonly Web3 _web3; + private readonly HttpClient _httpClient; + private readonly string _password = "!StrongPassword94"; + private readonly IEnumerable _subgraphs; + private Dictionary> _geckoPrices; + + public EvmManager(IEnumerable subgraphs) + { + var defaultChain = ChainService.GetEthereum(); + _web3 = new Web3(defaultChain.RpcUrl); + _httpClient = new HttpClient(); + _subgraphs = subgraphs; + _geckoPrices = _geckoPrices != null && _geckoPrices.Any() ? _geckoPrices : new Dictionary>(); + SetupPrices(); + } + + public void SetupPrices() + { + try + { + var geckoIds = new List(); + + foreach (var ticker in Enum.GetValues()) + { + var geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id; + if (geckoId != null) + { + geckoIds.Add(geckoId); + } + } + + if (geckoIds != null && geckoIds.Count > 0 && !_geckoPrices.Any()) + { + _geckoPrices = GetPrices(geckoIds).Result; + } + } + catch (Exception ex) + { + // TODO : Handle error + } + } + + public async Task GetAddressBalance(string address) + { + var balance = await _web3.Eth.GetBalance.SendRequestAsync(address); + var etherAmount = Web3.Convert.FromWei(balance.Value); + return etherAmount; + } + + public async Task> GetContractHolders(string contractAddress, DateTime since) + { + var holders = new List(); + var contract = _web3.Eth.ERC721.GetContractService(contractAddress); + + // Retrieve total supply to iterate over all token id generated within the contract + var totalSupply = await contract.TotalSupplyQueryAsync(); + + for (int tokenId = 1; tokenId < 10; tokenId++) + { + // Retrieve the owner of the nft + var tokenOwner = await contract.OwnerOfQueryAsync(tokenId); + + // If holder already have an nft we get the holder + // Otherwise we create a new holder + var holder = holders.FirstOrDefault(h => h.HolderAddress == tokenOwner) ?? new Holder(tokenOwner); + + // Retrieve all events related to the owner on the contract address + var nfts = await GetNftEvent(contractAddress, tokenOwner); + + // Get tokenId related event + var nftEvent = nfts.FirstOrDefault(n => n.Event.TokenId == new BigInteger(tokenId)); + + if (nftEvent != null) + { + // Retrieve the date of the nft event that occur in the blocknumber + var blockDate = await GetBlockDate(nftEvent.Log.BlockNumber); + var nft = new Nft(contractAddress, tokenId, blockDate); + + // Verify if the date of the holding is before the date passed from the parameters + if (blockDate <= since) + { + holder.AddNft(nft); + + // If holder do not exist in the list we add it + if (!holders.Exists(h => h.HolderAddress == tokenOwner)) + { + holders.Add(holder); + } + } + else + { + Console.WriteLine($"TokenId #{tokenId} for owner {tokenOwner} date not in range ({blockDate:f})"); + } + } + else + { + Console.WriteLine($"Error when getting tokenId #{tokenId} for owner {tokenOwner}"); + } + + UpdateLine(tokenId, (int)totalSupply, holders.Count); + } + + return holders; + } + + public async Task>> GetNftEvent(string contractAddress, string tokenOwner) + { + return await NftService.GetNftEvent(_web3, tokenOwner, contractAddress); + } + + public async Task GetBlockDate(int blockNumber) + { + return await GetBlockDate(new HexBigInteger(blockNumber)); + } + + public async Task GetBlockDate(HexBigInteger blockNumber) + { + var block = await _web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(blockNumber); + var date = DateHelpers.GetFromUnixTimestamp((int)block.Timestamp.Value); + return date; + } + + private void UpdateLine(int count, int total, int holders) + { + Console.WriteLine($"{count}/{total} - {(((decimal)count * 100m) / (decimal)total)} % - Holders : {holders}"); + } + + public string VerifySignature(string signature, string message) + { + var signer = new EthereumMessageSigner(); + var addressRecovered = signer.EncodeUTF8AndEcRecover(message, signature); + return addressRecovered; + } + + public string SignMessage(string message, string privateKey) + { + var signer = new EthereumMessageSigner(); + var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey)); + return signature; + } + + public (string Key, string Secret) GenerateAddress() + { + var mnemo = new Mnemonic(Wordlist.English, WordCount.Twelve); + var wallet = new Wallet(mnemo.ToString(), _password); + var account = wallet.GetAccount(0); + + return (account.Address, mnemo.ToString()); + } + + public string GetAddressFromMnemo(string mnemo) + { + var wallet = new Wallet(mnemo, _password); + return wallet.GetAccount(0).Address; + } + + public async Task GetEtherBalance(Domain.Evm.Chain chain, string account) + { + var web3 = new Web3(chain.RpcUrl); + var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account)); + var etherPrice = (await GetPrices(new List { "ethereum"}))["ethereum"]["usd"]; + + return new EvmBalance() { Balance = etherBalance, Price = etherPrice, TokenName = "ETH", Value = etherBalance * etherPrice }; + } + + public async Task> GetAllBalances(Domain.Evm.Chain chain, string publicAddress) + { + var balances = new List(); + + + var web3 = new Web3(chain.RpcUrl); + SetupPrices(); + foreach (var ticker in Enum.GetValues()) + { + try + { + var balance = await GetTokenBalance(chain.Name, ticker, publicAddress); + if (balance != null && balance.Balance > 0) + { + balances.Add(balance); + } + } + catch (Exception ex) + { + // TODO : handle exception + } + } + var etherBalance = await GetEtherBalance(chain, publicAddress); + etherBalance.Chain = chain; + balances.Add(etherBalance); + //var pageSize = 50; + //var tokenCount = TokenService.GetTokens().Count; + //for (int i = 0; i < (tokenCount / pageSize); i++) + //{ + // var pageBalances = await GetBalances(chain, i, pageSize, publicAddress).ConfigureAwait(false); + // balances.AddRange(pageBalances); + //} + + return balances; + } + + public async Task GetTokenBalance(string chainName, Ticker ticker, string publicAddress) + { + var chain = ChainService.GetChain(chainName); + var web3 = new Web3(chain.RpcUrl); + var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress }; + + //Creating a new query handler + var queryHandler = web3.Eth.GetContractQueryHandler(); + + var contractAddress = TokenService.GetContractAddress(ticker); + + if (contractAddress == Arbitrum.Address.Zero) + return null; + + var balance = await queryHandler + .QueryAsync(contractAddress, balanceOfMessage) + .ConfigureAwait(false); + + var geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id; + + if (geckoId == null) + return null; + + var tokenUsdPrice = _geckoPrices[geckoId][Constants.Stablecoins.Usd.ToLowerInvariant()]; + var tokenDecimal = TokenService.GetDecimal(ticker); + var balanceFromWei = Web3.Convert.FromWei(balance, tokenDecimal); + + var evmBalance = new EvmBalance + { + TokenName = ticker.ToString(), + Balance = balanceFromWei, + TokenAddress = contractAddress, + Value = tokenUsdPrice * balanceFromWei, + Price = tokenUsdPrice, + Chain = chain + }; + + return evmBalance; + } + + public async Task> GetBalances(Domain.Evm.Chain chain, int page, int pageSize, string publicAddress) + { + var callList = new List(); + var startItem = (page * pageSize); + var tokens = TokenService.GetTokens(); + var totaItemsToFetch = startItem + pageSize <= tokens.Count ? startItem + pageSize : tokens.Count + startItem; + + for (int i = startItem; i < totaItemsToFetch; i++) + { + var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress }; + var call = new MulticallInputOutput(balanceOfMessage, + tokens[i].Address); + callList.Add(call); + } + var evmTokens = new List<(EvmBalance Balance, GeckoToken GeckoToken)>(); + + try + { + var web3 = new Web3(chain.RpcUrl); + var geckoTokens = TokenService.GetGeckoTokens(); + + await web3.Eth.GetMultiQueryHandler().MultiCallAsync(callList.ToArray()); + + for (int i = startItem; i < totaItemsToFetch; i++) + { + var balance = ((MulticallInputOutput)callList[i - startItem]).Output.Balance; + if (balance > 0) + { + var tokenBalance = new EvmBalance() + { + Balance = Web3.Convert.FromWei(balance, tokens[i].Decimals), + TokenName = tokens[i].Symbol, + TokenAddress = tokens[i].Address, + Chain = chain + }; + + var geckoToken = geckoTokens.FirstOrDefault(x => string.Equals(x.Symbol, tokens[i].Symbol, StringComparison.InvariantCultureIgnoreCase)); + + evmTokens.Add((tokenBalance, geckoToken)); + } + } + + if (evmTokens.Count > 0) + { + var ids = evmTokens.Select(x => x.GeckoToken?.Id).Distinct().ToList(); + var prices = await GetPrices(ids).ConfigureAwait(false); + + foreach (var balance in evmTokens) + { + if (balance.GeckoToken != null) + { + var price = prices[balance.GeckoToken.Id.ToLower()]; + balance.Balance.Price = price["usd"]; + balance.Balance.Value = balance.Balance.Price * balance.Balance.Balance; + } + } + } + + } + catch (Exception ex) + { + // TODO : Handle error + // No enable to reach rpc + } + + return evmTokens.Select(e => e.Balance).ToList(); + } + + public async Task>> GetPrices(List geckoIds) + { + var idsCombined = string.Join(",", geckoIds); + return await _httpClient.GetFromJsonAsync>>("https://api.coingecko.com/api/v3/simple/price?ids=" + idsCombined + "&vs_currencies=usd"); + } + + public async Task> GetAllBalancesOnAllChain(string publicAddress) + { + var chainBalances = new List(); + var chains = ChainService.GetChains(); + + foreach (var chain in chains) + { + chainBalances.AddRange(await GetAllBalances(chain, publicAddress)); + } + + return chainBalances; + } + + public async Task> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe timeframe) + { + + string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe); + var gmxPrices = await _httpClient.GetFromJsonAsync($"https://stats.gmx.io/api/candles/{ticker}?preferableChainId=42161&period={gmxTimeframe}&from={startDate.ToUnixTimestamp()}&preferableSource=fast"); + //var subgraph = _subgraphs.First(s => s.GetProvider() == subgraphProvider); + //var prices = await subgraph.GetPrices(ticker, startDate, timeframe); + + //if (prices == null) + //{ + // foreach (var subgraphFallback in _subgraphs.Where(s => s.GetProvider() != subgraphProvider)) + // { + // prices = await subgraphFallback.GetPrices(ticker, startDate, timeframe); + + // if (prices != null) + // break; + // } + //} + + if (gmxPrices == null) + return null; + + gmxPrices.prices.RemoveAt(gmxPrices.prices.Count - 1); + return gmxPrices.prices.Select(p => GmxMappers.Map(p, ticker, timeframe)).ToList(); + } + + public decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker) + { + var subgraph = GetSubgraph(subgraphProvider); + var volume = subgraph.GetVolume(ticker).Result; + return volume; + } + + private ISubgraphPrices GetSubgraph(SubgraphProvider subgraphProvider) + { + return _subgraphs.First(s => s.GetProvider() == subgraphProvider); + } + + public async Task> GetAvailableTicker() + { + var subgraph = GetSubgraph(SubgraphProvider.Gbc); + var pairs = await subgraph.GetTickers(); + return pairs.ToList(); + } + + public async Task GetCandle(SubgraphProvider subgraphProvider, Ticker ticker) + { + var lastPrices = await GetCandles(subgraphProvider, ticker, DateTime.UtcNow.AddMinutes(-15), Timeframe.FiveMinutes); + return lastPrices.Last(); + } + + public async Task InitAddress(string chainName, string publicAddress, string privateKey) + { + try + { + var chain = ChainService.GetChain(chainName); + var account = new Wallet(privateKey, _password).GetAccount(publicAddress); + var web3 = new Web3(account, chain.RpcUrl); + var tickers = await GetAvailableTicker(); + await GmxService.InitAccountForTrading(web3, publicAddress, tickers); + return true; + } + catch (Exception ex) + { + return false; + } + } + + public async Task ApproveTicker(string publicAddress, string privateKey, Ticker ticker) + { + try + { + var account = new Wallet(privateKey, _password).GetAccount(publicAddress); + var contractAddress = TokenService.GetContractAddress(ticker); + await GmxService.ApproveToken(_web3, publicAddress, contractAddress); + return true; + } + catch (Exception ex) + { + return false; + } + } + + public async Task Send( + Domain.Evm.Chain chain, + Ticker ticker, + decimal amount, + string publicAddress, + string privateKey, + string receiverAddress) + { + var account = new Wallet(privateKey, _password).GetAccount(publicAddress); + var web3 = new Web3(account, chain.RpcUrl); + + try + { + if (ticker == Ticker.ETH) + { + return await SendEth(amount, receiverAddress, web3); + } + else + { + return await SendToken(ticker, amount, publicAddress, receiverAddress, web3); + } + } + catch (Exception ex) + { + return false; + } + } + + private static async Task SendEth(decimal amount, string receiverAddress, Web3 web3) + { + web3.TransactionManager.UseLegacyAsDefault = true; + var ethService = web3.Eth.GetEtherTransferService(); + var gas = await ethService.EstimateGasAsync(receiverAddress, amount); + var transaction = await ethService.TransferEtherAndWaitForReceiptAsync(receiverAddress, amount, gas: gas); + + return transaction.Status.Value == 1; + } + + private static async Task SendToken( + Ticker ticker, + decimal amount, + string senderAddress, + string receiverAddress, + Web3 web3) + { + + var contractAddress = TokenService.GetContractAddress(ticker); + var transactionMessage = new TransferFunction + { + FromAddress = senderAddress, + To = receiverAddress, + Value = Web3.Convert.ToWei(amount) + }; + + var transferHandler = web3.Eth.GetContractTransactionHandler(); + var transferReceipt = + await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transactionMessage); + + var transaction = await web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(transferReceipt.TransactionHash); + return transaction != null; + } + + public async Task CancelOrders(Account account, Ticker ticker) + { + var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key); + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(wallet, chain.RpcUrl); + return await GmxService.CancelOrders(web3, account.Key, ticker); + } + + public async Task IncreasePosition( + Account account, + Ticker ticker, + TradeDirection direction, + decimal price, + decimal quantity, + decimal? leverage) + { + var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key); + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(wallet, chain.RpcUrl); + + Trade trade = null; + try + { + trade = await GmxService.IncreasePosition(web3, account.Key, ticker, direction, price, quantity, leverage); + + } + catch (Exception ex) + { + throw; + } + + return trade; + } + + public async Task DecreasePosition( + Account account, + Ticker ticker, + TradeDirection direction, + decimal price, + decimal quantity, + decimal? leverage) + { + var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key); + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(wallet, chain.RpcUrl); + + Trade trade = null; + try + { + trade = await GmxService.DecreasePosition(web3, account.Key, ticker, direction, price, quantity, leverage); + } + catch (Exception ex) + { + throw; + } + + return trade; + } + + public async Task DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage) + { + + var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key); + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(wallet, chain.RpcUrl); + + Trade trade = null; + try + { + trade = await GmxService.DecreaseOrder(web3, tradeType, account.Key, ticker, direction, price, quantity, leverage); + } + catch (Exception ex) + { + throw; + } + + return trade; + } + + public async Task GetTrade(Account account, string chainName, Ticker ticker) + { + return await GetTrade(account.Key, chainName, ticker); + } + + + public async Task GetTrade(string reference, string chainName, Ticker ticker) + { + var chain = ChainService.GetChain(chainName); + var web3 = new Web3(chain.RpcUrl); + return await GmxService.GetTrade(web3, reference, ticker); + } + + public async Task QuantityInPosition(string chainName, string publicAddress, Ticker ticker) + { + var chain = ChainService.GetChain(chainName); + var web3 = new Web3(chain.RpcUrl); + var quantity = await GmxService.QuantityInPosition(web3, publicAddress, ticker); + return quantity; + } + + public async Task GetFee(string chainName) + { + var chain = ChainService.GetChain(chainName); + var web3 = new Web3(chain.RpcUrl); + var etherPrice = (await GetPrices(new List { "ethereum" }))["ethereum"]["usd"]; + var fee = await GmxService.GetFee(web3, etherPrice); + return fee; + } + + public async Task> GetOrders(Account account, Ticker ticker) + { + var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key); + var chain = ChainService.GetChain(Constants.Chains.Arbitrum); + var web3 = new Web3(wallet, chain.RpcUrl); + var orders = await GmxService.GetOrders(web3, account.Key, ticker); + + return GmxHelpers.Map(orders, ticker); + } + +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Web3/Extensions/PriceExtensions.cs b/src/Managing.Infrastructure.Web3/Extensions/PriceExtensions.cs new file mode 100644 index 0000000..79b312c --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Extensions/PriceExtensions.cs @@ -0,0 +1,75 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Infrastructure.Evm.Subgraphs.Models; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Extensions; + +public static class PriceExtensions +{ + public static List GetCandles(this Round[] prices, Timeframe timeframe, Ticker ticker) + { + int timezoneOffset = - (int)(new DateTimeOffset(DateTime.UtcNow).Offset.TotalSeconds); + var CHART_PERIODS = new Dictionary + { + { Timeframe.FiveMinutes, 60 * 5 }, + { Timeframe.FifteenMinutes, 60 * 15 }, + { Timeframe.OneHour, 60 * 60 }, + { Timeframe.FourHour, 60 * 60 * 4 }, + { Timeframe.OneDay, 60 * 60 * 24 } + }; + int periodTime = CHART_PERIODS[timeframe]; + + if (prices.Count() < 2) + { + return new List(); + } + + List candles = new List(); + Round first = prices[0]; + int prevTsGroup = (int)Math.Floor((decimal)first.UnixTimestamp / periodTime) * periodTime; + decimal prevPrice = decimal.Parse(first.Value); + decimal o = prevPrice; + decimal h = prevPrice; + decimal l = prevPrice; + decimal c = prevPrice; + for (int i = 1; i < prices.Count(); i++) + { + var current = prices[i]; + int tsGroup = (int)Math.Floor((decimal)current.UnixTimestamp / periodTime) * periodTime; + if (prevTsGroup != tsGroup) + { + candles.Add(new Candle + { + OpenTime = DateHelpers.GetFromUnixTimestamp(prevTsGroup + timezoneOffset), + Date = DateHelpers.GetFromUnixTimestamp(tsGroup + timezoneOffset), + Open = o, + High = h, + Low = l, + Close = c, + Timeframe = timeframe + }); + o = c; + h = Math.Max(o, c); + l = Math.Min(o, c); + } + c = decimal.Parse(current.Value); + h = Math.Max(h, c); + l = Math.Min(l, c); + prevTsGroup = tsGroup; + } + + return candles.Select(x => new Candle + { + OpenTime = x.OpenTime, + Date = x.Date, + Open = x.Open, + Close = x.Close, + High = x.High, + Low = x.Low, + Timeframe = x.Timeframe, + Exchange = TradingExchanges.Evm, + Ticker = ticker.ToString() + }).ToList(); + } +} diff --git a/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj b/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj new file mode 100644 index 0000000..a3bfd05 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj @@ -0,0 +1,26 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/src/Managing.Infrastructure.Web3/Models/GeckoToken.cs b/src/Managing.Infrastructure.Web3/Models/GeckoToken.cs new file mode 100644 index 0000000..6335182 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/GeckoToken.cs @@ -0,0 +1,7 @@ +namespace Managing.Domain.Evm; + +public class GeckoToken +{ + public string Id { get; set; } + public string Symbol { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs new file mode 100644 index 0000000..b64ded9 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs @@ -0,0 +1,19 @@ +using System.Numerics; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Models.Gmx; + +public class GmxOrder +{ + public string CollateralToken { get; internal set; } + public string IndexToken { get; internal set; } + public string CollateralDelta { get; internal set; } + public string SizeDelta { get; internal set; } + public string PurchaseToken { get; internal set; } + public string PurchaseTokenAmount { get; internal set; } + public bool IsLong { get; internal set; } + public string TriggerPrice { get; internal set; } + public bool TriggerAboveThreshold { get; internal set; } + public GmxOrderType Type { get; internal set; } + public BigInteger Index { get; internal set; } +} diff --git a/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrderIndex.cs b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrderIndex.cs new file mode 100644 index 0000000..d795149 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrderIndex.cs @@ -0,0 +1,9 @@ +namespace Managing.Infrastructure.Evm.Models.Gmx; + +public class GmxOrderIndexes +{ + public int SwapIndex { get; set; } + public int IncreaseIndex { get; set; } + public int DecreaseIndex { get; set; } + +} diff --git a/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs new file mode 100644 index 0000000..721b8cf --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +namespace Managing.Infrastructure.Evm.Models.Gmx; + +public class GmxPosition +{ + public string CollateralToken { get; set; } + public string IndexToken { get; set; } + public bool IsLong { get; set; } + public BigInteger SizeDelta { get; set; } + public BigInteger Collateral { get; set; } + public BigInteger AveragePrice { get; set; } + public BigInteger EntryFundingRate { get; set; } + public bool HasRealisedProfit { get; set; } + public BigInteger RealisedPnl { get; set; } + public BigInteger HasProfit { get; set; } + public BigInteger Delta { get; set; } + public BigInteger LastIncreasedTime { get; internal set; } +} diff --git a/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs new file mode 100644 index 0000000..addb9ae --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs @@ -0,0 +1,19 @@ +namespace Managing.Infrastructure.Evm.Models.Gmx; + +public class GmxPrices +{ + public List prices { get; set; } + public string period { get; set; } + public int updatedAt { get; set; } +} + + + +public class GmxOhlc +{ + public int t { get; set; } + public double o { get; set; } + public double c { get; set; } + public double h { get; set; } + public double l { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Models/TradaoList.cs b/src/Managing.Infrastructure.Web3/Models/TradaoList.cs new file mode 100644 index 0000000..478a7a7 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/TradaoList.cs @@ -0,0 +1,20 @@ +namespace Managing.Infrastructure.Evm.Models; + +public class TradaoList +{ + public IList row { get; set; } + public int total { get; set; } + public int updatetime { get; set; } +} + +public class Row +{ + public string user { get; set; } + public string pnl { get; set; } + public string roi { get; set; } + public string longSize { get; set; } + public string shortSize { get; set; } + public string openLatestCollateralSum { get; set; } + public string chainId { get; set; } + +} diff --git a/src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs b/src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs new file mode 100644 index 0000000..02c7a49 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs @@ -0,0 +1,49 @@ +namespace Managing.Infrastructure.Evm.Models; + +public class TradaoUserDetails +{ + public int updatetime { get; set; } + public Summary summary { get; set; } + public IList preference { get; set; } + public IList openPositions { get; set; } +} + + +public class Summary +{ + public string pnl { get; set; } + public string roi { get; set; } + public double winRate { get; set; } + public int trades { get; set; } + public int winTrades { get; set; } + public int lossTrades { get; set; } + public string averageWin { get; set; } + public string averageLoss { get; set; } + public string openCollateralSum { get; set; } + public string fee { get; set; } + +} +public class Preference +{ + public string tokenAddress { get; set; } + public int winTrades { get; set; } + public int lossTrades { get; set; } + public string pnl { get; set; } +} + +public class OpenPositions +{ + public string indexTokenAddress { get; set; } + public string collateralTokenAddress { get; set; } + public bool isLong { get; set; } + public string position { get; set; } + public string collateral { get; set; } + public string realizedPnl { get; set; } + public string averagePrice { get; set; } + public string totalFee { get; set; } + public string entryFundingRate { get; set; } + public string borrowFee { get; set; } + public string closeFee { get; set; } + public string liqPrice { get; set; } + +} diff --git a/src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs b/src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs new file mode 100644 index 0000000..7f6586f --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs @@ -0,0 +1,57 @@ +namespace Managing.Infrastructure.Evm.Referentials; + +public class Arbitrum +{ + public class Address + { + public const string ETH = "0x82af49447d8a07e3bd95bd0d56f35241523fbab1"; + public const string WBTC = "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"; + public const string LINK = "0xf97f4df75117a78c1a5a0dbb814af92458539fb4"; + public const string UNI = "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0"; + + public const string USDC = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8"; + public const string USDT = "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"; + public const string DAI = "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"; + public const string MIM = "0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A"; + public const string FRAX = "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F"; + + public const string Vault = "0x489ee077994B6658eAfA855C308275EAd8097C4A"; + public const string VaultPriceFeed = "0x2d68011bcA022ed0E474264145F46CC4de96a002"; + public const string Router = "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064"; + public const string VaultReader = "0xfebB9f4CAC4cD523598fE1C5771181440143F24A"; + public const string Reader = "0xF09eD52638c22cc3f1D7F5583e3699A075e601B2"; + public const string GlpManager = "0x321F653eED006AD1C29D174e17d96351BDe22649"; + public const string RewardRouter = "0xc73d553473dC65CE56db96c58e6a091c20980fbA"; + public const string RewardReader = "0xe725Ad0ce3eCf68A7B93d8D8091E83043Ff12e9A"; + + public const string GLP = "0x4277f8f2c384827b5273592ff7cebd9f2c1ac258"; + public const string GMX = "0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a"; + public const string ES_GMX = "0xf42ae1d54fd613c9bb14810b0588faaa09a426ca"; + public const string BN_GMX = "0x35247165119B69A40edD5304969560D0ef486921"; + public const string USDG = "0x45096e7aA921f27590f8F19e457794EB09678141"; + + public const string StakedGmxTracker = "0x908C4D94D34924765f1eDc22A1DD098397c59dD4"; + public const string BonusGmxTracker = "0x4d268a7d4C16ceB5a606c173Bd974984343fea13"; + public const string FeeGmxTracker = "0xd2D1162512F927a7e282Ef43a362659E4F2a728F"; + public const string FeeGlpTracker = "0x4e971a87900b931fF39d1Aad67697F49835400b6"; + public const string StakedGlpTracker = "0x1aDDD80E6039594eE970E5872D247bf0414C8903"; + + public const string StakedGmxDistributor = "0x23208B91A98c7C1CD9FE63085BFf68311494F193"; + public const string StakedGlpDistributor = "0x60519b48ec4183a61ca2B8e37869E675FD203b34"; + + public const string GmxVester = "0x199070DDfd1CFb69173aa2F7e20906F26B363004"; + public const string GlpVester = "0xA75287d2f8b217273E7FCD7E86eF07D33972042E"; + + public const string OrderBook = "0x09f77E8A13De9a35a7231028187e9fD5DB8a2ACB"; + public const string OrderExecutor = "0x7257ac5D0a0aaC04AA7bA2AC0A6Eb742E332c3fB"; + public const string OrderBookReader = "0xa27C20A7CF0e1C68C0460706bB674f98F362Bc21"; + + public const string FastPriceFeed = "0x1a0ad27350cccd6f7f168e052100b4960efdb774"; + public const string PositionRouter = "0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868"; + public const string PositionManager = "0x87a4088Bd721F83b6c2E5102e2FA47022Cb1c831"; + + public const string UniswapGmxEthPool = "0x80A9ae39310abf666A87C743d6ebBD0E8C42158E"; + + public static string Zero = "0x0000000000000000000000000000000000000000"; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/ChainService.cs b/src/Managing.Infrastructure.Web3/Services/ChainService.cs new file mode 100644 index 0000000..b73792e --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/ChainService.cs @@ -0,0 +1,70 @@ +using Managing.Common; +using Managing.Domain.Evm; + +namespace Managing.Infrastructure.Evm.Services; + +public static class ChainService +{ + //private const string RPC_ARBITRUM = "https://convincing-smart-arm.arbitrum-mainnet.discover.quiknode.pro/561ad3fa1db431a2c728c2fdb1a62e8f94acf703/"; + private const string RPC_ARBITRUM = "https://arb1.arbitrum.io/rpc"; + private const string RPC_ARBITRUM_GOERLI = "https://arb-goerli.g.alchemy.com/v2/ZMkIiKtNvgY03UtWOjho0oqkQrNt_pyc"; + private const string RPC_ETHEREUM = "https://mainnet.infura.io/v3/58f44d906ab345beadd03dd2b76348af"; + private const string RPC_ETHEREUM_GOERLI = "https://eth-goerli.g.alchemy.com/v2/xbc-eM-vxBmM9Uf1-RjjGjLp8Ng-FIc6"; + + public static Chain GetChain(string chainName) + { + if (string.IsNullOrEmpty(chainName)) + throw new Exception("Chain name is null or empty"); + + return GetChains().FirstOrDefault(c => c.Name == chainName); + } + + public static List GetChains() + { + var chains = new List() + { + GetArbitrum(), + GetEthereum(), + //GetArbitrumGoerli(), + //GetGoerli() + }; + + return chains; + } + + public static Chain GetArbitrum() + { + return new Chain() + { + Name = Constants.Chains.Arbitrum, + RpcUrl = RPC_ARBITRUM + }; + } + + public static Chain GetEthereum() + { + return new Chain() + { + Name = Constants.Chains.Ethereum, + RpcUrl = RPC_ETHEREUM + }; + } + + public static Chain GetArbitrumGoerli() + { + return new Chain() + { + Name = Constants.Chains.ArbitrumGoerli, + RpcUrl = RPC_ARBITRUM_GOERLI + }; + } + + public static Chain GetGoerli() + { + return new Chain() + { + Name = Constants.Chains.Goerli, + RpcUrl = RPC_ETHEREUM_GOERLI + }; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs new file mode 100644 index 0000000..a193df0 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs @@ -0,0 +1,139 @@ +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm.Models.Gmx; +using Managing.Infrastructure.Evm.Referentials; +using Nethereum.Web3; +using System.Numerics; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Services.Gmx; + +public static class GmxHelpers +{ + + public static decimal GetQuantityForLeverage(decimal quantity, decimal? leverage) + { + return leverage.HasValue ? leverage.Value * quantity : quantity; + } + + public static (List CollateralTokens, List IndexTokens, List IsLong) GetPositionQueryData(List contractAddress) + { + var collateralToken = new List(); + var indexTokens = new List(); + var isLongs = new List(); + + foreach (var token in contractAddress) + { + collateralToken.Add(token); + indexTokens.Add(token); + isLongs.Add(true); + } + + foreach (var token in contractAddress) + { + collateralToken.Add(Arbitrum.Address.USDC); + indexTokens.Add(token); + isLongs.Add(false); + } + + return (collateralToken, indexTokens, isLongs); + } + + public static List GetIndexesRange(int lastIndex) + { + var indexes = new List(); + + var limit = 15; + var from = (lastIndex - limit) < 0 ? 0 : lastIndex - limit; + + for (int i = from; i <= lastIndex; i++) + { + indexes.Add(new BigInteger(i)); + } + + return indexes; + } + + public static BigInteger GetAcceptablePrice(decimal price, bool isLong) + { + decimal priceBasisPoints; + var basisPointDivisor = 10000m; + var allowedSlippage = 34m; + var toDecimal = 30; + + if (isLong) + { + priceBasisPoints = basisPointDivisor - allowedSlippage; + } + else + { + priceBasisPoints = basisPointDivisor + allowedSlippage; + } + var test = Web3.Convert.ToWei(price, toDecimal) * new BigInteger(priceBasisPoints); + + var priceLimit = test / Web3.Convert.ToWei(basisPointDivisor, 0); + + return priceLimit; + } + + internal static BigInteger? GetGasLimit() + { + throw new NotImplementedException(); + } + + internal static List Map(List orders, Ticker ticker) + { + return orders.ConvertAll(order => Map(order, ticker)); + + } + private static Trade Map(GmxOrder order, Ticker ticker) + { + var trade = new Trade(DateTime.UtcNow, + order.IsLong ? TradeDirection.Short : TradeDirection.Long, + TradeStatus.Requested, + GetTradeType(order.IsLong, order.TriggerAboveThreshold), + ticker, + Convert.ToDecimal(order.SizeDelta), + Convert.ToDecimal(order.TriggerPrice), + null, + "", "" + ); + + return trade; + } + + public static bool GetTriggerAboveThreshold(bool isLong, TradeType tradeType) + { + if ((isLong && tradeType == TradeType.TakeProfit) || + (!isLong && tradeType == TradeType.StopLoss)) + { + return true; + } + + return false; + } + + public static TradeType GetTradeType(bool isLong, bool isAboveThreshold) + { + if ((isLong && isAboveThreshold) || + (!isLong && !isAboveThreshold)) + { + return TradeType.TakeProfit; + } + + return TradeType.StopLoss; + } + + internal static string GeTimeframe(Timeframe timeframe) + { + return timeframe switch + { + Timeframe.FiveMinutes => "5m", + Timeframe.FifteenMinutes => "15m", + Timeframe.ThirtyMinutes => "30m", + Timeframe.OneHour => "1h", + Timeframe.FourHour => "4h", + Timeframe.OneDay => "1d", + _ => throw new NotImplementedException(), + }; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs new file mode 100644 index 0000000..73d8c72 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs @@ -0,0 +1,177 @@ +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm.Models.Gmx; +using Managing.Infrastructure.Evm.Referentials; +using Nethereum.Web3; +using System.Numerics; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Services.Gmx; + +public static class GmxMappers +{ + internal static Trade Map(GmxPosition? position, Ticker ticker) + { + if (position == null) + { + return null; + } + + var leverage = position.SizeDelta / position.Collateral; + + var trade = new Trade( + DateHelpers.GetFromUnixTimestamp((int)position.LastIncreasedTime), + position.IsLong ? TradeDirection.Long : TradeDirection.Short, + TradeStatus.Filled, + TradeType.Limit, + ticker, + Web3.Convert.FromWei(position.SizeDelta, 30), + Web3.Convert.FromWei(position.AveragePrice, 30), + (decimal)leverage, + "", + ""); + + return trade; + } + + public static List MapPositions(List positionData, + (List CollateralTokens, List IndexTokens, List IsLong) queryData) + { + var gmxPositions = new List(); + var propLength = 9; + + for (int i = 0; i < queryData.CollateralTokens.Count; i++) + { + var gmxPosition = new GmxPosition + { + CollateralToken = queryData.CollateralTokens[i], + IndexToken = queryData.IndexTokens[i], + IsLong = queryData.IsLong[i], + SizeDelta = positionData[i * propLength], + Collateral = positionData[i * propLength + 1], + AveragePrice = positionData[i * propLength + 2], + EntryFundingRate = positionData[i * propLength + 3], + //gmxPosition.CumulativeFundingRate = collateralToken.cumulativeFundingRate; + HasRealisedProfit = positionData[i * propLength + 4].Equals(1), + RealisedPnl = positionData[i * propLength + 5], + LastIncreasedTime = positionData[i * propLength + 6], + HasProfit = positionData[i * propLength + 7], + Delta = positionData[i * propLength + 8] + }; + + if (!gmxPosition.SizeDelta.IsZero) + gmxPositions.Add(gmxPosition); + } + + return gmxPositions; + } + + public static List MapIncrease( + List orderData, + List addressData, + List indexes) + { + var extractor = (List orderProperty, List address) => + { + var order = new GmxOrder + { + PurchaseToken = address[0].ToString(), + CollateralToken = address[1].ToString(), + IndexToken = address[2].ToString(), + PurchaseTokenAmount = Web3.Convert.FromWeiToBigDecimal(orderProperty[0], 18).ToString(), + SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(), + IsLong = orderProperty[2].ToString() == "1", + TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(), + TriggerAboveThreshold = orderProperty[4].ToString() == "1", + Type = GmxOrderType.Increase + }; + + return order; + }; + + return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 3); + } + + public static List MapDecrease( + List orderData, + List addressData, + List indexes) + { + var extractor = (List orderProperty, List address) => + { + var order = new GmxOrder + { + CollateralToken = address[0], + IndexToken = address[1], + CollateralDelta = orderProperty[0].ToString(), + SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(), + IsLong = orderProperty[2].ToString() == "1", + TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(), + TriggerAboveThreshold = orderProperty[4].ToString() == "1", + Type = GmxOrderType.Decrease + }; + + return order; + }; + + return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 2); + } + + public static List ParseOrdersData( + List orderData, + List addressData, + Func, List, GmxOrder> extractor, + List indexes, + int uintPropsLength, + int addressPropsLength) + { + if (orderData.Count == 0 || addressData.Count == 0) + { + return new List(); + } + + var count = orderData.Count / uintPropsLength; + + var orders = new List(); + for (int i = 0; i < count; i++) + { + var slicedAddress = addressData + .Skip(addressPropsLength * i) + .Take(addressPropsLength) + .ToList(); + + if (slicedAddress[0] == Arbitrum.Address.Zero && slicedAddress[1] == Arbitrum.Address.Zero) + { + continue; + } + + var slicedProperty = orderData + .Skip(uintPropsLength * i) + .Take(uintPropsLength * i) + .ToList(); + + var order = extractor(slicedProperty, slicedAddress); + order.Index = indexes[i]; + orders.Add(order); + } + + return orders; + } + + internal static Candle Map(GmxOhlc price, Ticker ticker, Timeframe timeframe) + { + return new Candle() + { + Date = DateHelpers.GetFromUnixTimestamp(price.t), + OpenTime = DateHelpers.GetFromUnixTimestamp(price.t).AddSeconds(-1), + Open = Convert.ToDecimal(price.o), + High = Convert.ToDecimal(price.h), + Low = Convert.ToDecimal(price.l), + Close = Convert.ToDecimal(price.c), + Exchange = TradingExchanges.Evm, + Ticker = ticker.ToString(), + Timeframe = timeframe + }; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs new file mode 100644 index 0000000..318aa1a --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs @@ -0,0 +1,458 @@ +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm.Models.Gmx; +using Managing.Infrastructure.Evm.Referentials; +using Managing.Tools.OrderBook; +using Managing.Tools.OrderBook.ContractDefinition; +using Managing.Tools.OrderBookReader; +using Managing.Tools.OrderBookReader.ContractDefinition; +using Managing.Tools.PositionRouter; +using Managing.Tools.PositionRouter.ContractDefinition; +using Managing.Tools.Reader; +using Managing.Tools.Reader.ContractDefinition; +using Managing.Tools.Router; +using Managing.Tools.Router.ContractDefinition; +using Nethereum.Contracts.Standards.ERC20; +using Nethereum.Contracts.Standards.ERC20.ContractDefinition; +using Nethereum.Util; +using Nethereum.Web3; +using System.Numerics; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Services.Gmx; + +public static class GmxService +{ + private const decimal _orderFeesExecution = 0.0003m; + private const decimal _positionUpdateFees = 0.0001m; + + public async static Task InitAccountForTrading(Web3 web3, string publicAddress, List tickers) + { + var router = new RouterService(web3, Arbitrum.Address.Router); + if (!await IsPluginAdded(web3, publicAddress, Arbitrum.Address.PositionRouter)) + { + var routerApproval = await router + .ApprovePluginRequestAndWaitForReceiptAsync(Arbitrum.Address.PositionRouter); + } + + + if (!await IsPluginAdded(web3, publicAddress, Arbitrum.Address.OrderBook)) + { + var routerApproval = await router + .ApprovePluginRequestAsync(Arbitrum.Address.OrderBook); + } + + foreach (var ticker in tickers) + { + var conntractAddress = TokenService.GetContractAddress(ticker); + await ApproveToken(web3, publicAddress, conntractAddress); + } + } + + public async static Task IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress) + { + var router = new RouterService(web3, Arbitrum.Address.Router); + var function = new ApprovedPluginsFunction + { + ReturnValue1 = publicAddress, + ReturnValue2 = pluginAddress + }; + + var isAdded = await router.ApprovedPluginsQueryAsync(function); + return isAdded; + } + + public async static Task ApproveToken(Web3 web3, string publicAddress, string contractAddress) + { + var input = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction + { + Spender = publicAddress + }; + + var contract = new ERC20ContractService(web3.Eth, contractAddress); + var approval = await contract.ApproveRequestAsync(input); + } + + public static async Task ApproveOrder(Web3 web3, Ticker ticker, string publicAddress, decimal amount) + { + var contractAddress = TokenService.GetContractAddress(ticker); + var contract = new ERC20ContractService(web3.Eth, contractAddress); + + var allowanceQuery = new AllowanceFunction + { + Owner = publicAddress, + Spender = Arbitrum.Address.Router + }; + + var allowance = await contract.AllowanceQueryAsync(allowanceQuery); + + if (allowance.IsZero) return false; + + var approveQuery = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction + { + FromAddress = publicAddress, + Spender = Arbitrum.Address.Router, + Value = Web3.Convert.ToWei(amount) + }; + + var approval = await contract.ApproveRequestAsync(approveQuery); + return true; + } + + public async static Task IncreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage) + { + var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage); + var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook); + var contractAddress = TokenService.GetContractAddress(ticker); + var isLong = direction == TradeDirection.Long; + var function = new CreateIncreaseOrderFunction(); + + // Forcing path to use USDC to pay the trade + function.Path = new List { Arbitrum.Address.USDC }; + function.AmountIn = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$ + function.IndexToken = contractAddress; // Token to long/short + function.MinOut = new BigInteger(0); + // Size of the position with the leveraged quantity + // Ex : Long 11$ x3. SizeDelta = 33$ + function.SizeDelta = Web3.Convert.ToWei(quantityLeveraged * price, UnitConversion.EthUnit.Tether); + function.CollateralToken = Arbitrum.Address.USDC; // USDC + function.IsLong = isLong; + function.TriggerPrice = Web3.Convert.ToWei(price, 30); // Price of the order execution + function.TriggerAboveThreshold = false; + function.ExecutionFee = Web3.Convert.ToWei(_orderFeesExecution); // Fee required to execute tx + function.ShouldWrap = false; + + // Specify the tx opts + function.AmountToSend = Web3.Convert.ToWei(_orderFeesExecution); + function.FromAddress = publicAddress; + //function.MaxFeePerGas = await orderBook.ContractHandler.EstimateGasAsync(function); + function.GasPrice = GetGasPrice(); + + // Approving Router to transfer ERC20 token + var approval = await ApproveOrder(web3, Ticker.USDC, publicAddress, _orderFeesExecution); + + if (!approval) return null; + + var receipt = await orderBook + .CreateIncreaseOrderRequestAndWaitForReceiptAsync(function); + + var trade = new Trade(DateTime.UtcNow, + direction, + TradeStatus.Requested, + TradeType.Limit, + ticker, + quantity, + price, + leverage, + receipt.TransactionHash, + ""); + + return trade; + } + + public async static Task DecreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage) + { + var trade = new Trade(DateTime.UtcNow, + direction, + TradeStatus.Cancelled, + TradeType.Market, + ticker, + quantity, + price, + leverage, + "", + ""); + + // Check if there is quantity in position + if (await QuantityInPosition(web3, publicAddress, ticker) == 0) return trade; + + var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage); + var positionRouter = new PositionRouterService(web3, Arbitrum.Address.PositionRouter); + var contractAddress = TokenService.GetContractAddress(ticker); + var isLong = direction == TradeDirection.Long; + var function = new CreateDecreasePositionFunction(); + + // Forcing path to use contract address to widthdraw funds + // The address for closing a short, should be USDC + //function.Path = new List { contractAddress }; + function.Path = new List { Arbitrum.Address.USDC }; + // the index token of the position + function.IndexToken = contractAddress; // Token to long/short + // the amount of collateral in USD value to withdraw + function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$ + //function.CollateralDelta = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$ + // the USD value of the change in position size + function.SizeDelta = Web3.Convert.ToWei(quantity, UnitConversion.EthUnit.Tether); + function.IsLong = isLong; + // the address to receive the withdrawn tokens + function.Receiver = publicAddress; + // the USD value of the min (for longs) or max (for shorts) index price acceptable when executing the request + function.AcceptablePrice = GmxHelpers.GetAcceptablePrice(price, isLong); + // the min output token amount + function.MinOut = new BigInteger(0); + function.ExecutionFee = Web3.Convert.ToWei(_positionUpdateFees); // Fee required to execute tx + function.WithdrawETH = false; + function.CallbackTarget = Arbitrum.Address.Zero; + + // Specify the tx opts + function.AmountToSend = Web3.Convert.ToWei(_positionUpdateFees); + function.FromAddress = publicAddress; + function.MaxFeePerGas = await positionRouter.ContractHandler.EstimateGasAsync(function); + function.GasPrice = GetGasPrice(); + + var approval = await ApproveOrder(web3, ticker, publicAddress, _positionUpdateFees); + + if (!approval) return null; + + var receipt = await positionRouter + .CreateDecreasePositionRequestAndWaitForReceiptAsync(function); + + trade.SetExchangeOrderId(receipt.TransactionHash); + trade.SetStatus(receipt.Status.Value.IsOne ? TradeStatus.Requested : TradeStatus.Cancelled); + + return trade; + } + + public static async Task DecreaseOrder(Web3 web3, TradeType tradeType, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage) + { + var trade = new Trade(DateTime.UtcNow, + direction, + TradeStatus.Cancelled, + tradeType, + ticker, + quantity, + price, + leverage, + "", + ""); + + // Check if there is quantity in position + var currentPosition = await GetGmxPosition(web3, publicAddress, ticker); + + if (currentPosition == null || currentPosition?.SizeDelta == 0) return trade; + + var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage); + var orderbook = new OrderBookService(web3, Arbitrum.Address.OrderBook); + var contractAddress = TokenService.GetContractAddress(ticker); + var isLong = direction != TradeDirection.Long; + var function = new CreateDecreaseOrderFunction(); + + // the index token of the position + function.IndexToken = contractAddress; // Token to long/short + // the USD value of the change in position size + function.SizeDelta = currentPosition.SizeDelta; + function.CollateralToken = Arbitrum.Address.USDC; + // the amount of collateral in USD value to withdraw + function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$ + //function.CollateralDelta = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$ + function.IsLong = isLong; + // the USD value of the min (for longs) or max (for shorts) index price acceptable when executing the request + function.TriggerPrice = GmxHelpers.GetAcceptablePrice(price, isLong); + function.TriggerAboveThreshold = GmxHelpers.GetTriggerAboveThreshold(isLong, tradeType); + + // Specify the tx opts + function.AmountToSend = Web3.Convert.ToWei(_orderFeesExecution); + function.FromAddress = publicAddress; + function.MaxFeePerGas = await orderbook.ContractHandler.EstimateGasAsync(function); + function.GasPrice = GetGasPrice(); + + var approval = await ApproveOrder(web3, ticker, publicAddress, _positionUpdateFees); + + if (!approval) return null; + + var receipt = await orderbook + .CreateDecreaseOrderRequestAndWaitForReceiptAsync(function); + + trade.SetExchangeOrderId(receipt.TransactionHash); + trade.SetStatus(TradeStatus.Requested); + return trade; + } + + public async static Task CancelOrders(Web3 web3, string publicAddress, Ticker ticker) + { + var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook); + var orders = await GetOrders(web3, publicAddress, ticker); + + if (!orders.Any()) return true; + + var function = new CancelMultipleFunction(); + var increaseOrderIndexes = orders.Where(i => i.Type == GmxOrderType.Increase); + function.IncreaseOrderIndexes = increaseOrderIndexes.Select(o => o.Index).ToList(); + + var decreaseOrderIndexes = orders.Where(i => i.Type == GmxOrderType.Decrease); + function.DecreaseOrderIndexes = decreaseOrderIndexes.Select(o => o.Index).ToList(); + + function.SwapOrderIndexes = new List(); + + try + { + if (function.DecreaseOrderIndexes.Any() || function.IncreaseOrderIndexes.Any()) + { + function.MaxFeePerGas = await orderBook.ContractHandler.EstimateGasAsync(function); + function.GasPrice = GetGasPrice(); + + var cancellation = await orderBook.CancelMultipleRequestAndWaitForReceiptAsync(function); + } + } + catch (Exception ex) + { + return false; + } + + return true; + } + + private static BigInteger GetGasPrice() + { + return Web3.Convert.ToWei(0.1, UnitConversion.EthUnit.Gwei); + } + + public static async Task> GetOrders(Web3 web3, string publicAddress, Ticker ticker) + { + var lastIndexes = await GetLastIndex(web3, publicAddress); + var orders = new List(); + var orderBookReader = new OrderBookReaderService(web3, Arbitrum.Address.OrderBookReader); + + var increaseOrders = await GetIncreaseOrders(orderBookReader, publicAddress, lastIndexes.IncreaseIndex); + var decreaseOrders = await GetDecreaseOrders(orderBookReader, publicAddress, lastIndexes.DecreaseIndex); + + orders.AddRange(increaseOrders); + orders.AddRange(decreaseOrders); + var contractAddress = TokenService.GetContractAddress(ticker); + var ordersFiltered = orders.Where(o => string.Equals(o.IndexToken, contractAddress, StringComparison.CurrentCultureIgnoreCase)); + + return ordersFiltered.ToList(); + } + + private static async Task> GetIncreaseOrders(OrderBookReaderService orderBookReader, string publicAddress, int lastIndex) + { + var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex); + var increaseOrdersFunction = new GetIncreaseOrdersFunction + { + OrderBookAddress = Arbitrum.Address.OrderBook, + Account = publicAddress, + Indices = increaseIndex, + }; + + var increaseOrders = await orderBookReader.GetIncreaseOrdersQueryAsync(increaseOrdersFunction); + + return GmxMappers.MapIncrease(increaseOrders.ReturnValue1, increaseOrders.ReturnValue2, increaseIndex); + } + + private static async Task> GetDecreaseOrders(OrderBookReaderService orderBookReader, string publicAddress, int lastIndex) + { + var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex); + var increaseOrdersFunction = new GetDecreaseOrdersFunction + { + OrderBookAddress = Arbitrum.Address.OrderBook, + Account = publicAddress, + Indices = increaseIndex, + }; + + var increaseOrders = await orderBookReader.GetDecreaseOrdersQueryAsync(increaseOrdersFunction); + + return GmxMappers.MapDecrease(increaseOrders.ReturnValue1, increaseOrders.ReturnValue2, increaseIndex); + } + + public static async Task GetLastIndex(Web3 web3, string publicAddress) + { + var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook); + var increaseFunction = new IncreaseOrdersIndexFunction + { + ReturnValue1 = publicAddress + }; + var decreaseFunction = new DecreaseOrdersIndexFunction + { + ReturnValue1 = publicAddress + }; + var swapFunction = new SwapOrdersIndexFunction + { + ReturnValue1 = publicAddress + }; + + var increaseIndex = await orderBook.IncreaseOrdersIndexQueryAsync(increaseFunction); + var decreaseIndex = await orderBook.DecreaseOrdersIndexQueryAsync(decreaseFunction); + var swapIndex = await orderBook.SwapOrdersIndexQueryAsync(swapFunction); + + var indexes = new GmxOrderIndexes + { + SwapIndex = (int)swapIndex > 0 ? (int)swapIndex - 1 : (int)swapIndex, + IncreaseIndex = (int)increaseIndex > 0 ? (int)increaseIndex - 1 : (int)increaseIndex, + DecreaseIndex = (int)decreaseIndex > 0 ? (int)decreaseIndex - 1 : (int)decreaseIndex + }; + + return indexes; + } + + public static async Task GetTrade(Web3 web3, string publicAddress, Ticker ticker) + { + var position = await GetGmxPosition(web3, publicAddress, ticker); + return GmxMappers.Map(position, ticker); + } + + public static async Task GetGmxPosition(Web3 web3, string publicAddress, Ticker ticker) + { + var reader = new ReaderService(web3, Arbitrum.Address.Reader); + var contractAddress = TokenService.GetContractAddress(ticker); + var queryData = GmxHelpers.GetPositionQueryData(new List { contractAddress }); + + var function = new GetPositionsFunction + { + Vault = Arbitrum.Address.Vault, + Account = publicAddress, + CollateralTokens = queryData.CollateralTokens, + IndexTokens = queryData.IndexTokens, + IsLong = queryData.IsLong + }; + + var result = await reader.GetPositionsQueryAsync(function); + + var positions = GmxMappers.MapPositions(result, queryData); + var position = positions.FirstOrDefault(p => p.IndexToken == contractAddress); + return position; + } + + public static async Task QuantityInPosition(Web3 web3, string key, Ticker ticker) + { + var position = await GetTrade(web3, key, ticker); + return position?.Quantity ?? 0m; + } + + public static async Task GetFee(Web3 web3, decimal ethPrice) + { + var positionRouter = new PositionRouterService(web3, Arbitrum.Address.PositionRouter); + var contractAddress = TokenService.GetContractAddress(Ticker.BTC); + var function = new CreateDecreasePositionFunction(); + + function.Path = new List { contractAddress }; + function.IndexToken = contractAddress; // Token to long/short + function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$ + function.SizeDelta = Web3.Convert.ToWei(100, UnitConversion.EthUnit.Tether); + function.IsLong = true; + function.Receiver = Arbitrum.Address.Zero; + function.AcceptablePrice = GmxHelpers.GetAcceptablePrice(100, true); + function.MinOut = new BigInteger(0); + function.ExecutionFee = Web3.Convert.ToWei(_positionUpdateFees); // Fee required to execute tx + function.WithdrawETH = false; + function.CallbackTarget = Arbitrum.Address.Zero; + + function.AmountToSend = Web3.Convert.ToWei(_positionUpdateFees); + function.FromAddress = Arbitrum.Address.Zero; + + var totalCost = 0m; + + try + { + var gasCost = await positionRouter.ContractHandler.EstimateGasAsync(function); + var gasPrice = GetGasPrice(); + var gas = gasPrice * gasCost; + totalCost = ethPrice * Web3.Convert.FromWei(gas, 18); + return totalCost; + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + + return totalCost; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/NftService.cs b/src/Managing.Infrastructure.Web3/Services/NftService.cs new file mode 100644 index 0000000..9e1fa19 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/NftService.cs @@ -0,0 +1,45 @@ +using Nethereum.Contracts; +using Nethereum.Contracts.Standards.ERC721.ContractDefinition; +using Nethereum.Web3; + +namespace Managing.Infrastructure.Evm.Services; + +public static class NftService +{ + public static async Task>> GetNftEvent(Web3 web3, string owner, string contract) + { + try + { + // Retrieve transfer event of the contract + var transferEvent = web3.Eth.GetEvent(contract); + + // Create IN & OUT filter to filter the return transfers changes + var transferFilterIn = transferEvent.CreateFilterInput(null, owner); + var transferFilterOut = transferEvent.CreateFilterInput(owner); + + // Retrieve changes based on filter + var transferInLogs = await transferEvent.GetAllChangesAsync(transferFilterIn); + var transferOutLogs = await transferEvent.GetAllChangesAsync(transferFilterOut); + + var list = new List>(); + + // For each transfer IN, we add the event into the list + foreach (var ins in transferInLogs) + { + list.Add(ins); + } + + // Remove all transfer OUT of the list because the use might already send the token + foreach (var ins in transferOutLogs) + { + list.Remove(ins); + } + return list; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return null; + } + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/SubgraphService.cs b/src/Managing.Infrastructure.Web3/Services/SubgraphService.cs new file mode 100644 index 0000000..7ac3fd0 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/SubgraphService.cs @@ -0,0 +1,45 @@ +using GraphQL.Client.Http; +using GraphQL.Client.Serializer.SystemTextJson; +using Managing.Domain.Evm; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Services; + +public static class SubgraphService +{ + private const string SUBGRAPH_UNISWAP_V2 = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2"; + private const string SUBGRAPH_CHAINLINK = "https://api.thegraph.com/subgraphs/name/openpredict/chainlink-prices-subgraph"; + private const string SUBGRAPH_CHAINLINK_GMX = "https://api.thegraph.com/subgraphs/name/deividask/chainlink"; + private const string SUBGRAPH_GBC = "https://api.thegraph.com/subgraphs/name/nissoh/gmx-arbitrum"; + + public static GraphQLHttpClient GetSubgraphClient(SubgraphProvider subgraphProvider) + { + var url = GetSubgraph(subgraphProvider).Url; + var graphQLOptions = new GraphQLHttpClientOptions + { + EndPoint = new Uri(url) + }; + return new GraphQLHttpClient(graphQLOptions, new SystemTextJsonSerializer()); + } + + private static Subgraph GetSubgraph(SubgraphProvider subgraphProvider) + { + return new Subgraph() + { + SubgraphProvider = subgraphProvider, + Url = GetSubgraphUrl(subgraphProvider) + }; + } + + private static string GetSubgraphUrl(SubgraphProvider subgraphProvider) + { + return subgraphProvider switch + { + SubgraphProvider.UniswapV2 => SUBGRAPH_UNISWAP_V2, + SubgraphProvider.ChainlinkPrice => SUBGRAPH_CHAINLINK, + SubgraphProvider.ChainlinkGmx => SUBGRAPH_CHAINLINK_GMX, + SubgraphProvider.Gbc => SUBGRAPH_GBC, + _ => throw new Exception("No url for subgraphprovider") + }; + } +} diff --git a/src/Managing.Infrastructure.Web3/Services/TokenService.cs b/src/Managing.Infrastructure.Web3/Services/TokenService.cs new file mode 100644 index 0000000..6a29d7f --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/TokenService.cs @@ -0,0 +1,65 @@ +using Managing.Domain.Evm; +using Managing.Infrastructure.Evm.Referentials; +using Nethereum.Contracts.Standards.ERC20.TokenList; +using Newtonsoft.Json; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Services; + +public static class TokenService +{ + public static string TokensJSON = @"{""name"":""Defiprime"",""logoURI"":""https://defiprime.com/images/defiprime-logo-hires2.png"",""keywords"":[""Defi"",""defiprime"",""curated""],""tags"":{""defi"":{""name"":""DeFi Tokens"",""description"":""Tokens associated with the products listed at defiprime.com""}},""timestamp"":""2022-11-29T00:00:00+00:00"",""tokens"":[{""chainId"":1,""address"":""0xa1d65E8fB6e87b60FECCBc582F7f97804B725521"",""symbol"":""DXD"",""name"":""DXdao"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F"",""symbol"":""SNX"",""name"":""Synthetix"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e"",""symbol"":""YFI"",""name"":""Yearn.finance"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x27054b13b1B798B345b591a4d22e6562d47eA75a"",""symbol"":""AST"",""name"":""AirSwap"",""decimals"":4,""tags"":[""defi""]},{""chainId"":1,""address"":""0xba100000625a3754423978a60c9317c58a424e3D"",""symbol"":""BAL"",""name"":""Balancer"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C"",""symbol"":""BNT"",""name"":""Bancor"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xd533a949740bb3306d119cc777fa900ba034cd52"",""symbol"":""CRV"",""name"":""Curve"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xcc80c051057b774cd75067dc48f8987c4eb97a5e"",""symbol"":""NEC"",""name"":""DeversiFi"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xB705268213D593B8FD88d3FDEFF93AFF5CbDcfAE"",""symbol"":""IDEX"",""name"":""IDEX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xdeFA4e8a7bcBA345F687a2f1456F5Edd9CE97202"",""symbol"":""KNC"",""name"":""KyberSwap"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD"",""symbol"":""LRC"",""name"":""Loopring Exchange"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xe41d2489571d322189246dafa5ebde1f4699f498"",""symbol"":""ZRX"",""name"":""Matcha"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x56d811088235F11C8920698a204A5010a788f4b3"",""symbol"":""BZRX"",""name"":""bZx"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x514910771AF9Ca656af840dff83E8264EcF986CA"",""symbol"":""LINK"",""name"":""Chainlink"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xec67005c4e498ec7f55e092bd1d35cbc47c91892"",""symbol"":""MLN"",""name"":""Melon Protocol"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x408e41876cccdc0f92210600ef50372656052a38"",""symbol"":""REN"",""name"":""Ren"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828"",""symbol"":""UMA"",""name"":""Uma"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x0d438f3b5175bebc262bf23753c1e53d03432bde"",""symbol"":""wNXM"",""name"":""Nexus Mutual"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x107c4504cd79c5d2696ea0030a8dd4e92601b82e"",""symbol"":""BLT"",""name"":""Bloom"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x41e5560054824eA6B0732E656E3Ad64E20e94E45"",""symbol"":""CVC"",""name"":""Civic"",""decimals"":8,""tags"":[""defi""]},{""chainId"":1,""address"":""0xebbdf302c940c6bfd49c6b165f457fdb324649bc"",""symbol"":""HYDRO"",""name"":""Hydro"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x4cc19356f2d37338b9802aa8e8fc58b0373296e7"",""symbol"":""KEY"",""name"":""SelfKey"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x80fB784B7eD66730e8b1DBd9820aFD29931aab03"",""symbol"":""LEND"",""name"":""Aave"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xc00e94cb662c3520282e6f5717214004a7f26888"",""symbol"":""COMP"",""name"":""Compound"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2"",""symbol"":""MKR"",""name"":""MakerDAO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x4e352cf164e64adcbad318c3a1e222e9eba4ce42"",""symbol"":""MCB"",""name"":""MCDEX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x0abdace70d3790235af448c88547603b945604ea"",""symbol"":""DNT"",""name"":""District0x"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xfca59cd816ab1ead66534d82bc21e7515ce441cf"",""symbol"":""RARI"",""name"":""Rarible"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"",""symbol"":""MATIC"",""name"":""Matic"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"",""symbol"":""OMG"",""name"":""OmiseGO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x0Ae055097C6d159879521C384F1D2123D1f195e6"",""symbol"":""STAKE"",""name"":""xDai Stable Chain"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6810e776880c02933d47db1b9fc05908e5386b96"",""symbol"":""GNO"",""name"":""Gnosis"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xd46ba6d942050d489dbd938a2c909a5d5039a161"",""symbol"":""AMPL"",""name"":""Ampleforth"",""decimals"":9,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6b175474e89094c44da98b954eedeac495271d0f"",""symbol"":""DAI"",""name"":""DAI"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x4f3afec4e5a3f2a6a1a411def7d7dfe50ee057bf"",""symbol"":""DGX"",""name"":""Digix"",""decimals"":9,""tags"":[""defi""]},{""chainId"":1,""address"":""0x056fd409e1d7a124bd7017459dfea2f387b6d5cd"",""symbol"":""GUSD"",""name"":""Gemini Dollar"",""decimals"":2,""tags"":[""defi""]},{""chainId"":1,""address"":""0x8e870d67f660d95d5be530380d0ec0bd388289e1"",""symbol"":""USDP"",""name"":""Pax Dollar"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"",""symbol"":""USDC"",""name"":""USD Coin"",""decimals"":6,""tags"":[""defi""]},{""chainId"":1,""address"":""0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"",""symbol"":""WBTC"",""name"":""WBTC"",""decimals"":8,""tags"":[""defi""]},{""chainId"":1,""address"":""0xdf574c24545e5ffecb9a659c229253d4111d87e1"",""symbol"":""HUSD"",""name"":""HUSD"",""decimals"":8,""tags"":[""defi""]},{""chainId"":1,""address"":""0x5BC25f649fc4e26069dDF4cF4010F9f706c23831"",""symbol"":""DUSD"",""name"":""DefiDollar"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6b3595068778dd592e39a122f4f5a5cf09c90fe2"",""symbol"":""SUSHI"",""name"":""SushiSwap"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"",""symbol"":""UNI"",""name"":""Uniswap"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x543ff227f64aa17ea132bf9886cab5db55dcaddf"",""symbol"":""GEN"",""name"":""DAOstack"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xa0246c9032bc3a600820415ae600c6388619a14d"",""symbol"":""FARM"",""name"":""Harvest"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"",""symbol"":""AAVE"",""name"":""AAVE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x43dfc4159d86f3a37a5a4b3d4580b888ad7d4ddd"",""symbol"":""DODO"",""name"":""DODO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xa117000000f279D81A1D3cc75430fAA017FA5A2e"",""symbol"":""ANT"",""name"":""Aragon"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x4688a8b1f292fdab17e9a90c8bc379dc1dbd8713"",""symbol"":""COVER"",""name"":""COVER"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x36f3fd68e7325a35eb768f1aedaae9ea0689d723"",""symbol"":""ESD"",""name"":""Empty Set Dollar"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xeF9Cd7882c067686691B6fF49e650b43AFBBCC6B"",""symbol"":""FNX"",""name"":""FinNexus"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xca1207647ff814039530d7d35df0e1dd2e91fa84"",""symbol"":""DHT"",""name"":""dHEDGE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xfFffFffF2ba8F66D4e51811C5190992176930278"",""symbol"":""COMBO"",""name"":""Furucombo"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x853d955aCEf822Db058eb8505911ED77F175b99e"",""symbol"":""FRAX"",""name"":""Frax"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0"",""symbol"":""FXS"",""name"":""Frax Share"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x86772b1409b61c639EaAc9Ba0AcfBb6E238e5F83"",""symbol"":""NDX"",""name"":""Indexed"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b"",""symbol"":""DPI"",""name"":""DefiPulse Index"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xad32a8e6220741182940c5abf610bde99e737b2d"",""symbol"":""DOUGH"",""name"":""PieDAO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x0000000000095413afc295d19edeb1ad7b71c952"",""symbol"":""LON"",""name"":""Tokenlon"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x888888888889c00c67689029d7856aac1065ec11"",""symbol"":""OPIUM"",""name"":""OPIUM"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x09a3ecafa817268f77be1283176b946c4ff2e608"",""symbol"":""MIR"",""name"":""Mirror Protocol"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x8888801af4d980682e47f1a9036e589479e835c5"",""symbol"":""MPH"",""name"":""88mph"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x4c19596f5aaff459fa38b0f7ed92f11ae6543784"",""symbol"":""TRU"",""name"":""TrueFi"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x87d73e916d7057945c9bcd8cdd94e42a6f47f776"",""symbol"":""NFTX"",""name"":""NFTX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x429881672b9ae42b8eba0e26cd9c73711b891ca5"",""symbol"":""PICKLE"",""name"":""PICKLE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x875773784af8135ea0ef43b5a374aad105c5d39e"",""symbol"":""IDLE"",""name"":""IDLE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xd291e7a03283640fdc51b121ac401383a46cc623"",""symbol"":""RGT"",""name"":""RGT"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xa1faa113cbe53436df28ff0aee54275c13b40975"",""symbol"":""ALPHA"",""name"":""ALPHA"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x584bc13c7d411c00c01a62e8019472de68768430"",""symbol"":""HEGIC"",""name"":""HEGIC"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x49e833337ece7afe375e44f4e3e8481029218e5c"",""symbol"":""VALUE"",""name"":""VALUE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81"",""symbol"":""MUSE"",""name"":""MUSE"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x92e187a03b6cd19cb6af293ba17f2745fd2357d5"",""symbol"":""DUCK"",""name"":""DUCK"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x111111111117dc0aa78b770fa6a738034120c302"",""symbol"":""1INCH"",""name"":""1INCH"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x1b40183efb4dd766f11bda7a7c3ad8982e998421"",""symbol"":""VSP"",""name"":""VSP"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7"",""symbol"":""AKRO"",""name"":""Akropolis"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x03ab458634910aad20ef5f1c8ee96f1d6ac54919"",""symbol"":""RAI"",""name"":""RAI"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x77fba179c79de5b7653f68b5039af940ada60ce0"",""symbol"":""FORTH"",""name"":""FORTH"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xab37e1358b639fd877f015027bb62d3ddaa7557e"",""symbol"":""LIEN"",""name"":""LIEN"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6c28aef8977c9b773996d0e8376d2ee379446f2f"",""symbol"":""QUICK"",""name"":""QUICK"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xde30da39c46104798bb5aa3fe8b9e0e1f348163f"",""symbol"":""GTC"",""name"":""GTC"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xdbdb4d16eda451d0503b854cf79d55697f90c8df"",""symbol"":""ALCX"",""name"":""ALCX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d"",""symbol"":""LQTY"",""name"":""LQTY"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x5a98fcbea516cf06857215779fd812ca3bef1b32"",""symbol"":""LDO"",""name"":""LDO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6f40d4a6237c257fff2db00fa0510deeecd303eb"",""symbol"":""INST"",""name"":""INST"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x72f020f8f3e8fd9382705723cd26380f8d0c66bb"",""symbol"":""PLOT"",""name"":""PLOT"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x8f8221afbb33998d8584a2b05749ba73c37a938a"",""symbol"":""REQ"",""name"":""REQ"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x321c2fe4446c7c963dc41dd58879af648838f98d"",""symbol"":""CTX"",""name"":""CTX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x16c52ceece2ed57dad87319d91b5e3637d50afa4"",""symbol"":""TCAP"",""name"":""TCAP"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x92d6c1e31e14520e676a687f0a93788b716beff5"",""symbol"":""DYDX"",""name"":""DYDX"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x6123b0049f904d730db3c36a31167d9d4121fa6b"",""symbol"":""RBN"",""name"":""RBN"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x73968b9a57c6e53d41345fd57a6e6ae27d6cdb2f"",""symbol"":""SDT"",""name"":""SDT"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xcafe001067cdef266afb7eb5a286dcfd277f3de5"",""symbol"":""PSP"",""name"":""PSP"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5"",""symbol"":""OHM"",""name"":""OHM"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x72b886d09c117654ab7da13a14d603001de0b777"",""symbol"":""XDEFI"",""name"":""XDEFI"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0x3ec8798b81485a254928b70cda1cf0a2bb0b74d7"",""symbol"":""GRO"",""name"":""GRO"",""decimals"":18,""tags"":[""defi""]},{""chainId"":1,""address"":""0xf4d2888d29d722226fafa5d9b24f9164c092421e"",""symbol"":""LOOKS"",""name"":""LOOKS"",""decimals"":18,""tags"":[""defi""]},{""chainId"":42161,""address"":""0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a"",""symbol"":""GMX"",""name"":""GMX"",""tags"":[""defi""]},{""chainId"":1,""address"":""0x7778360f035c589fce2f4ea5786cbd8b36e5396b"",""symbol"":""OOE"",""name"":""OOE"",""tags"":[""defi""]},{""chainId"":1,""address"":""0xd9fcd98c322942075a5c3860693e9f4f03aae07b"",""symbol"":""EUL"",""name"":""EUL"",""tags"":[""defi""]},{""chainId"":1,""address"":""0x8290333cef9e6d528dd5618fb97a76f268f3edd4"",""symbol"":""ANKR"",""name"":""ANKR"",""tags"":[""defi""]},{""chainId"":10,""address"":""0x920cf626a271321c151d027030d5d08af699456b"",""symbol"":""KWENTA"",""name"":""KWENTA"",""tags"":[""defi""]}],""version"":{""major"":1,""minor"":45,""patch"":0}}"; + public static string GeckoJson = @"[{'Id':'blockstack','Symbol':'stx'},{'Id':'bitcoin','Symbol':'btc'},{'Id':'liquidity-dividends-protocol','Symbol':'LID'},{'Id':'uma','Symbol':'uma'},{'Id':'uptrennd','Symbol':'1up'},{'Id':'math','Symbol':'math'},{'Id':'dos-network','Symbol':'dos'},{'Id':'xdai-stake','Symbol':'stake'},{'Id':'tellor','Symbol':'trb'},{'Id':'yearn-finance','Symbol':'yfi'},{'Id':'streamr-datacoin','Symbol':'data'},{'Id':'wrapped-nxm','Symbol':'wnxm'},{'Id':'basic-attention-token','Symbol':'bat'},{'Id':'the-abyss','Symbol':'abyss'},{'Id':'decentraland','Symbol':'mana'},{'Id':'xio','Symbol':'xio'},{'Id':'grid','Symbol':'grid'},{'Id':'howdoo','Symbol':'udoo'},{'Id':'curio','Symbol':'cur'},{'Id':'tendies','Symbol':'tend'},{'Id':'numeraire','Symbol':'nmr'},{'Id':'owl','Symbol':'owl'},{'Id':'parachute','Symbol':'par'},{'Id':'reserve','Symbol':'rsv'},{'Id':'bancor','Symbol':'bnt'},{'Id':'sapien','Symbol':'spn'},{'Id':'wrapped-bitcoin','Symbol':'wbtc'},{'Id':'raiden-network','Symbol':'rdn'},{'Id':'toshify-finance','Symbol':'YFT'},{'Id':'idextools','Symbol':'dext'},{'Id':'airswap','Symbol':'ast'},{'Id':'yflink','Symbol':'yfl'},{'Id':'blitzpredict','Symbol':'xbp'},{'Id':'hex','Symbol':'hex'},{'Id':'cream-2','Symbol':'cream'},{'Id':'simple-token','Symbol':'ost'},{'Id':'bilira','Symbol':'tryb'},{'Id':'viberate','Symbol':'vib'},{'Id':'global-digital-content','Symbol':'gdc'},{'Id':'usd-bancor','Symbol':'usdb'},{'Id':'dark-energy-crystals','Symbol':'dec'},{'Id':'q-dao-governance-token-v1-0','Symbol':'qdao'},{'Id':'blockv','Symbol':'vee'},{'Id':'aidcoin','Symbol':'aid'},{'Id':'tokenbox','Symbol':'tbx'},{'Id':'peerex-network','Symbol':'PERX'},{'Id':'rivetz','Symbol':'rvt'},{'Id':'republic-protocol','Symbol':'ren'},{'Id':'oracolxor','Symbol':'xor'},{'Id':'funfair','Symbol':'fun'},{'Id':'civic','Symbol':'cvc'},{'Id':'spankchain','Symbol':'spank'},{'Id':'cap','Symbol':'cap'},{'Id':'v-id-blockchain','Symbol':'vidt'},{'Id':'vision','Symbol':'vsn'},{'Id':'libertas-token','Symbol':'libertas'},{'Id':'foam-protocol','Symbol':'foam'},{'Id':'usdq','Symbol':'usdq'},{'Id':'quant-network','Symbol':'qnt'},{'Id':'zinc','Symbol':'zinc'},{'Id':'ghost-by-mcafee','Symbol':'ghost'},{'Id':'key','Symbol':'key'},{'Id':'mini','Symbol':'mini'},{'Id':'mcdex','Symbol':'mcb'},{'Id':'digix-gold','Symbol':'dgx'},{'Id':'binance-usd','Symbol':'busd'},{'Id':'chainlink','Symbol':'link'},{'Id':'daostack','Symbol':'gen'},{'Id':'bzx-protocol','Symbol':'bzrx'},{'Id':'bluzelle','Symbol':'blz'},{'Id':'trust','Symbol':'trust'},{'Id':'livepeer','Symbol':'lpt'},{'Id':'power-ledger','Symbol':'powr'},{'Id':'dether','Symbol':'DTH'},{'Id':'cosplay-token','Symbol':'cot'},{'Id':'deviantcoin','Symbol':'dev'},{'Id':'cdai','Symbol':'cdai'},{'Id':'mybit-token','Symbol':'myb'},{'Id':'seth','Symbol':'seth'},{'Id':'defipie','Symbol':'PIE'},{'Id':'iexec-rlc','Symbol':'rlc'},{'Id':'linkart','Symbol':'lar'},{'Id':'martexcoin','Symbol':'mxt'},{'Id':'jetswap-token','Symbol':'wings'},{'Id':'smart-mfg','Symbol':'mfg'},{'Id':'gnosis','Symbol':'gno'},{'Id':'sirin-labs-token','Symbol':'srn'},{'Id':'bankroll-vault','Symbol':'vlt'},{'Id':'geeq','Symbol':'GEEQ'},{'Id':'unifi-protocol','Symbol':'up'},{'Id':'holotoken','Symbol':'hot'},{'Id':'polytrade','Symbol':'trade'},{'Id':'props','Symbol':'props'},{'Id':'amon','Symbol':'amn'},{'Id':'status','Symbol':'SNT'},{'Id':'boxx','Symbol':'boxx'},{'Id':'morpheus-network','Symbol':'mrph'},{'Id':'dfohub','Symbol':'buidl'},{'Id':'santiment-network-token','Symbol':'san'},{'Id':'robonomics-network','Symbol':'xrt'},{'Id':'ethlend','Symbol':'lend'},{'Id':'measurable-data-token','Symbol':'mdt'},{'Id':'origin-protocol','Symbol':'ogn'},{'Id':'atlantis-token','Symbol':'atis'},{'Id':'remme','Symbol':'rem'},{'Id':'goldmint','Symbol':'mntp'},{'Id':'unibright','Symbol':'ubt'},{'Id':'dia-data','Symbol':'DIA'},{'Id':'reserve-rights-token','Symbol':'rsr'},{'Id':'penta','Symbol':'pnt'},{'Id':'akropolis','Symbol':'akro'},{'Id':'nervenetwork','Symbol':'nvt'},{'Id':'swipe','Symbol':'sxp'},{'Id':'paxos-standard','Symbol':'pax'},{'Id':'request-network','Symbol':'req'},{'Id':'orion-protocol','Symbol':'orn'},{'Id':'real','Symbol':'real'},{'Id':'kleros','Symbol':'pnk'},{'Id':'lock-token','Symbol':'lock'},{'Id':'deipool','Symbol':'dip'},{'Id':'ocean-protocol','Symbol':'ocean'},{'Id':'strong','Symbol':'strong'},{'Id':'polymath-network','Symbol':'poly'},{'Id':'digital-rand','Symbol':'dzar'},{'Id':'eth-rsi-60-40-yield-set','Symbol':'ethrsiapy'},{'Id':'maker','Symbol':'mkr'},{'Id':'usd-coin','Symbol':'usdc'},{'Id':'pundi-x','Symbol':'npxs'},{'Id':'yfii-finance','Symbol':'yfii'},{'Id':'dxdao','Symbol':'dxd'},{'Id':'meta','Symbol':'mta'},{'Id':'metronome','Symbol':'met'},{'Id':'equus-mining-token','Symbol':'eqmt'},{'Id':'stableusd','Symbol':'USDS'},{'Id':'loom-network-new','Symbol':'loom'},{'Id':'agrinovuscoin','Symbol':'agri'},{'Id':'celsius-degree-token','Symbol':'cel'},{'Id':'tokencard','Symbol':'tkn'},{'Id':'transcodium','Symbol':'tns'},{'Id':'ceek','Symbol':'ceek'},{'Id':'compound-0x','Symbol':'czrx'},{'Id':'cryptofranc','Symbol':'xchf'},{'Id':'rocket-pool','Symbol':'rpl'},{'Id':'perlin','Symbol':'perl'},{'Id':'stonk','Symbol':'stonk'},{'Id':'bitsou','Symbol':'btu'},{'Id':'release-ico-project','Symbol':'rel'},{'Id':'balancer','Symbol':'bal'},{'Id':'band-protocol','Symbol':'band'},{'Id':'pangea','Symbol':'xpat'},{'Id':'loopring','Symbol':'lrc'},{'Id':'ink-protocol','Symbol':'xnk'},{'Id':'meter-governance-mapped-by-meter-io','Symbol':'eMTRG'},{'Id':'kardiachain','Symbol':'kai'},{'Id':'storm','Symbol':'stmx'},{'Id':'aelf','Symbol':'elf'},{'Id':'compound-coin','Symbol':'comp'},{'Id':'havven','Symbol':'snx'},{'Id':'aleph','Symbol':'aleph'},{'Id':'weth','Symbol':'weth'},{'Id':'compound-wrapped-btc','Symbol':'cwbtc'},{'Id':'auctus','Symbol':'auc'},{'Id':'lamden','Symbol':'tau'},{'Id':'quadrant-protocol','Symbol':'equad'},{'Id':'trendering','Symbol':'trnd'},{'Id':'gifto','Symbol':'gto'},{'Id':'zzz-finance','Symbol':'zzz'},{'Id':'trustswap','Symbol':'swap'},{'Id':'nectar-token','Symbol':'nec'},{'Id':'anj','Symbol':'anj'},{'Id':'yffi-finance','Symbol':'yffi'},{'Id':'cbi-index-7','Symbol':'cbix7'},{'Id':'machix','Symbol':'mcx'},{'Id':'omisego','Symbol':'omg'},{'Id':'ong','Symbol':'ong'},{'Id':'ampleforth','Symbol':'ampl'},{'Id':'cindicator','Symbol':'cnd'},{'Id':'fintrux','Symbol':'ftx'},{'Id':'dfohub','Symbol':'buidl'},{'Id':'sociall','Symbol':'scl'},{'Id':'pluton','Symbol':'plu'},{'Id':'tether','Symbol':'usdt'},{'Id':'stasis-eurs','Symbol':'eurs'},{'Id':'kyber-network','Symbol':'kncl'},{'Id':'mainframe','Symbol':'mft'},{'Id':'husd','Symbol':'husd'},{'Id':'karma-dao','Symbol':'karma'},{'Id':'rmpl','Symbol':'rmpl'},{'Id':'shipchain','Symbol':'ship'},{'Id':'pillar','Symbol':'plr'},{'Id':'0x','Symbol':'zrx'},{'Id':'2key','Symbol':'2key'},{'Id':'renbtc','Symbol':'renbtc'},{'Id':'melon','Symbol':'mln'},{'Id':'zippie','Symbol':'zipt'},{'Id':'askobar-network','Symbol':'asko'},{'Id':'ethereum-vault','Symbol':'ethv'},{'Id':'finnexus','Symbol':'fnx'},{'Id':'evo','Symbol':'evo'},{'Id':'flixxo','Symbol':'flixx'},{'Id':'pamp-cc','Symbol':'PAMP'},{'Id':'hedgetrade','Symbol':'hedg'},{'Id':'dmst','Symbol':'dmst'},{'Id':'unicrypt','Symbol':'unc'},{'Id':'unipower','Symbol':'power'},{'Id':'metal','Symbol':'mtl'},{'Id':'enjincoin','Symbol':'enj'},{'Id':'compound-usdt','Symbol':'cusdt'},{'Id':'indorse','Symbol':'ind'},{'Id':'antiample','Symbol':'xamp'},{'Id':'ripio-credit-network','Symbol':'rcn'},{'Id':'trueaud','Symbol':'taud'},{'Id':'truegbp','Symbol':'tgbp'},{'Id':'truehkd','Symbol':'thkd'},{'Id':'gastoken','Symbol':'gst2'},{'Id':'chai','Symbol':'chai'},{'Id':'compound-basic-attention-token','Symbol':'cbat'},{'Id':'compound-sai','Symbol':'csai'},{'Id':'compound-ether','Symbol':'ceth'},{'Id':'compound-usd-coin','Symbol':'cusdc'},{'Id':'compound-augur','Symbol':'crep'},{'Id':'leo-token','Symbol':'leo'},{'Id':'huobi-token','Symbol':'ht'},{'Id':'matic-network','Symbol':'matic'},{'Id':'dai','Symbol':'dai'},{'Id':'sai','Symbol':'sai'},{'Id':'nusd','Symbol':'susd'},{'Id':'seur','Symbol':'seur'},{'Id':'ibtc','Symbol':'iBTC'},{'Id':'sbtc','Symbol':'sbtc'},{'Id':'saud','Symbol':'saud'},{'Id':'scex','Symbol':'scex'},{'Id':'sada','Symbol':'sada'},{'Id':'sdash','Symbol':'sdash'},{'Id':'seos','Symbol':'seos'},{'Id':'setc','Symbol':'setc'},{'Id':'sxmr','Symbol':'sxmr'},{'Id':'sxrp','Symbol':'sxrp'},{'Id':'sxag','Symbol':'sxag'},{'Id':'sltc','Symbol':'sltc'},{'Id':'ieth','Symbol':'ieth'},{'Id':'sdefi','Symbol':'sdefi'},{'Id':'sxau','Symbol':'sxau'},{'Id':'sbnb','Symbol':'sbnb'},{'Id':'sxtz','Symbol':'sxtz'},{'Id':'shiba-link','Symbol':'slink'},{'Id':'ibnb-2','Symbol':'ibnb'},{'Id':'ieos','Symbol':'ieos'},{'Id':'dollars','Symbol':'usdx'},{'Id':'true-usd','Symbol':'tusd'},{'Id':'trustline-network','Symbol':'tln'},{'Id':'lunch-money','Symbol':'lmy'},{'Id':'ybusd','Symbol':'ybusd'},{'Id':'ytusd','Symbol':'ytusd'},{'Id':'blockchain-certified-data-token','Symbol':'bcdt'},{'Id':'lendroid-support-token','Symbol':'lst'},{'Id':'marketpeak','Symbol':'peak'},{'Id':'pantos','Symbol':'pan'},{'Id':'gemini-dollar','Symbol':'gusd'},{'Id':'proton','Symbol':'xpr'},{'Id':'keep-network','Symbol':'keep'},{'Id':'renzec','Symbol':'renzec'},{'Id':'renbch','Symbol':'renbch'},{'Id':'t-bitcoin','Symbol':'tbtc'},{'Id':'huobi-btc','Symbol':'hbtc'},{'Id':'shuffle-monster','Symbol':'shuf'},{'Id':'donut','Symbol':'donut'},{'Id':'chi-gastoken','Symbol':'chi'},{'Id':'switch','Symbol':'esh'},{'Id':'pax-gold','Symbol':'paxg'},{'Id':'0xmonero','Symbol':'0xmr'},{'Id':'storj','Symbol':'storj'},{'Id':'salt','Symbol':'salt'},{'Id':'curve-fi-ydai-yusdc-yusdt-ytusd','Symbol':'yCurve'},{'Id':'rarible','Symbol':'rari'},{'Id':'pareto-network','Symbol':'pareto'},{'Id':'plutus-defi','Symbol':'plt'},{'Id':'ptokens-btc','Symbol':'pbtc'},{'Id':'serum','Symbol':'srm'},{'Id':'autonio','Symbol':'niox'},{'Id':'defi-stoa','Symbol':'sta'},{'Id':'falcon-token','Symbol':'fnt'},{'Id':'yam-2','Symbol':'yam'},{'Id':'addax','Symbol':'adx'},{'Id':'curve-dao-token','Symbol':'crv'},{'Id':'darwinia-network-native-token','Symbol':'ring'},{'Id':'cartesi','Symbol':'ctsi'},{'Id':'unilayer','Symbol':'layer'},{'Id':'degenerator','Symbol':'meme'},{'Id':'origintrail','Symbol':'trac'},{'Id':'yam-v2','Symbol':'YAMv2'},{'Id':'jarvis-reward-token','Symbol':'jrt'},{'Id':'neutrino','Symbol':'usdn'},{'Id':'parsiq','Symbol':'prq'},{'Id':'hakka-finance','Symbol':'hakka'},{'Id':'robonomics-web-services','Symbol':'rws'},{'Id':'growth-defi','Symbol':'gro'},{'Id':'concentrated-voting-power','Symbol':'cvp'},{'Id':'ethopt','Symbol':'opt'},{'Id':'sushi','Symbol':'sushi'},{'Id':'stacktical','Symbol':'dsla'},{'Id':'swapfolio','Symbol':'swfl'},{'Id':'fsw-token','Symbol':'fsw'},{'Id':'akropolis-delphi','Symbol':'adel'},{'Id':'swerve-dao','Symbol':'swrv'},{'Id':'multiplier','Symbol':'mxx'},{'Id':'genesis-vision','Symbol':'gvt'},{'Id':'step-finance','Symbol':'step'},{'Id':'safe-coin','Symbol':'safe'},{'Id':'predix-network','Symbol':'prdx'},{'Id':'defipulse-index','Symbol':'dpi'},{'Id':'aavegotchi','Symbol':'ghst'},{'Id':'unicorn-token','Symbol':'uni'},{'Id':'game-x-coin','Symbol':'gxc'},{'Id':'pickle-finance','Symbol':'pickle'},{'Id':'frontier-token','Symbol':'front'},{'Id':'dhedge-dao','Symbol':'dht'},{'Id':'harvest-finance','Symbol':'farm'},{'Id':'golff','Symbol':'gof'},{'Id':'xbtc','Symbol':'xbtc'},{'Id':'origin-dollar','Symbol':'ousd'},{'Id':'aave','Symbol':'aave'},{'Id':'dodo','Symbol':'dodo'},{'Id':'safe2','Symbol':'safe2'},{'Id':'spaceswap-shake','Symbol':'shake'},{'Id':'spaceswap-milk2','Symbol':'milk2'},{'Id':'cvault-finance','Symbol':'core'},{'Id':'perpetual-protocol','Symbol':'perp'},{'Id':'value-liquidity','Symbol':'value'},{'Id':'sparkle','Symbol':'sprkl'},{'Id':'usdk','Symbol':'usdk'},{'Id':'swag-finance','Symbol':'swag'},{'Id':'piedao-dough-v2','Symbol':'dough'},{'Id':'kush-finance','Symbol':'kseed'},{'Id':'ccomp','Symbol':'ccomp'},{'Id':'compound-uniswap','Symbol':'cuni'},{'Id':'quras-token','Symbol':'xqc'},{'Id':'master-usd','Symbol':'musd'},{'Id':'zeroswap','Symbol':'zee'},{'Id':'hegic','Symbol':'hegic'},{'Id':'definer','Symbol':'fin'},{'Id':'astro','Symbol':'astro'},{'Id':'amp-token','Symbol':'amp'},{'Id':'barnbridge','Symbol':'bond'},{'Id':'antcoin','Symbol':'ant'},{'Id':'fuse-network-token','Symbol':'fuse'},{'Id':'empty-set-dollar','Symbol':'esd'},{'Id':'keep3rv1','Symbol':'kp3r'},{'Id':'defidollar','Symbol':'dusd'},{'Id':'aurora-dao','Symbol':'idex'},{'Id':'nix-bridge-token','Symbol':'voice'},{'Id':'hermez-network-token','Symbol':'hez'},{'Id':'surfexutilitytoken','Symbol':'surf'},{'Id':'wrapped-anatha','Symbol':'wanatha'},{'Id':'audius','Symbol':'audio'},{'Id':'atari','Symbol':'atri'},{'Id':'index-cooperative','Symbol':'index'},{'Id':'powertrade-fuel','Symbol':'ptf'},{'Id':'defidollar-dao','Symbol':'dfd'},{'Id':'apy-finance','Symbol':'apy'},{'Id':'geyser','Symbol':'gysr'},{'Id':'keep4r','Symbol':'kp4r'},{'Id':'axie-infinity','Symbol':'axs'},{'Id':'smart-valor','Symbol':'valor'},{'Id':'allianceblock','Symbol':'albt'},{'Id':'tomoe','Symbol':'tomoe'},{'Id':'lua-token','Symbol':'lua'},{'Id':'holyheld','Symbol':'holy'},{'Id':'polkastarter','Symbol':'pols'},{'Id':'rio-defi','Symbol':'rfuel'},{'Id':'unlend-finance','Symbol':'uft'},{'Id':'lgcy-network','Symbol':'lgcy'},{'Id':'rope-token','Symbol':'rope'},{'Id':'plotx','Symbol':'plot'},{'Id':'keysians-network','Symbol':'ken'},{'Id':'nsure-network','Symbol':'nsure'},{'Id':'chronobank','Symbol':'time'},{'Id':'saffron-finance','Symbol':'sfi'},{'Id':'88mph','Symbol':'mph'},{'Id':'oro','Symbol':'oro'},{'Id':'e-radix','Symbol':'exrd'},{'Id':'boosted-finance','Symbol':'boost'},{'Id':'dforce-token','Symbol':'df'},{'Id':'synlev','Symbol':'syn'},{'Id':'lto-network','Symbol':'lto'},{'Id':'synth-soil','Symbol':'soil'},{'Id':'cache-gold','Symbol':'cgt'},{'Id':'nucypher','Symbol':'nu'},{'Id':'octree','Symbol':'oct'},{'Id':'quiverx','Symbol':'qrx'},{'Id':'bitsong','Symbol':'btsg'},{'Id':'radium','Symbol':'val'},{'Id':'api3','Symbol':'api3'},{'Id':'basis-cash','Symbol':'bac'},{'Id':'basis-share','Symbol':'bas'},{'Id':'power-index-pool-token','Symbol':'pipt'},{'Id':'megacryptopolis','Symbol':'mega'},{'Id':'base-protocol','Symbol':'base'},{'Id':'bondly','Symbol':'bondly'},{'Id':'neutrino-system-base-token','Symbol':'nsbt'},{'Id':'nexo','Symbol':'nexo'},{'Id':'aave-aave','Symbol':'aAAVE'},{'Id':'aave-bat','Symbol':'abat'},{'Id':'aave-busd','Symbol':'abusd'},{'Id':'aave-dai','Symbol':'adai'},{'Id':'aave-enj','Symbol':'aenj'},{'Id':'aave-knc','Symbol':'aknc'},{'Id':'aave-link','Symbol':'alink'},{'Id':'aave-mana','Symbol':'amana'},{'Id':'aave-mkr','Symbol':'amkr'},{'Id':'aave-ren','Symbol':'aren'},{'Id':'aave-snx','Symbol':'asnx'},{'Id':'aave-susd','Symbol':'asusd'},{'Id':'aave-tusd','Symbol':'atusd'},{'Id':'aave-uni','Symbol':'auni'},{'Id':'aave-usdc','Symbol':'ausdc'},{'Id':'aave-usdt','Symbol':'ausdt'},{'Id':'aave-wbtc','Symbol':'awbtc'},{'Id':'aave-weth','Symbol':'aweth'},{'Id':'aave-yfi','Symbol':'aYFI'},{'Id':'aave-zrx','Symbol':'azrx'},{'Id':'coinlion','Symbol':'lion'},{'Id':'zlot','Symbol':'zlot'},{'Id':'ecofi','Symbol':'eco'},{'Id':'utrust','Symbol':'utk'},{'Id':'badger-dao','Symbol':'badger'},{'Id':'golden-ratio-token','Symbol':'grt'},{'Id':'lido-dao','Symbol':'ldo'},{'Id':'tornado-cash','Symbol':'torn'},{'Id':'staked-ether','Symbol':'steth'},{'Id':'mahadao','Symbol':'maha'},{'Id':'marlin','Symbol':'pond'},{'Id':'frax-share','Symbol':'fxs'},{'Id':'spice','Symbol':'spice'},{'Id':'1inch','Symbol':'1inch'},{'Id':'plasma-finance','Symbol':'ppay'},{'Id':'mithril-share','Symbol':'mis'},{'Id':'basiscoin-share','Symbol':'bcs'},{'Id':'exeedme','Symbol':'xed'},{'Id':'wozx','Symbol':'wozx'},{'Id':'defi-nation-signals-dao','Symbol':'dsd'},{'Id':'fox-finance','Symbol':'fox'},{'Id':'cover-protocol','Symbol':'cover'},{'Id':'wise-token11','Symbol':'wise'},{'Id':'fera','Symbol':'fera'},{'Id':'furucombo','Symbol':'combo'},{'Id':'usdfreeliquidity','Symbol':'usdfl'},{'Id':'fetch-ai','Symbol':'fet'},{'Id':'pha','Symbol':'pha'},{'Id':'pbtc35a','Symbol':'pbtc35a'},{'Id':'frax','Symbol':'frax'},{'Id':'injective-protocol','Symbol':'inj'},{'Id':'legolas-exchange','Symbol':'lgo'},{'Id':'yield','Symbol':'yld'},{'Id':'cyberfi','Symbol':'cfi'},{'Id':'rari-governance-token','Symbol':'rgt'},{'Id':'rook','Symbol':'rook'},{'Id':'yield-optimization-platform','Symbol':'yop'},{'Id':'nftx','Symbol':'nftx'},{'Id':'robbocoach','Symbol':'rbc'},{'Id':'stake-dao','Symbol':'sdt'},{'Id':'ethos','Symbol':'vgx'},{'Id':'debase','Symbol':'debase'},{'Id':'ankr','Symbol':'ankr'},{'Id':'thorchain','Symbol':'rune'},{'Id':'bao-finance','Symbol':'bao'},{'Id':'reef-finance','Symbol':'reef'},{'Id':'truebit-protocol','Symbol':'tru'},{'Id':'indexed-finance','Symbol':'ndx'},{'Id':'benchmark-protocol','Symbol':'mark'},{'Id':'zero-exchange','Symbol':'zero'},{'Id':'octofi','Symbol':'octo'},{'Id':'oraichain-token','Symbol':'orai'},{'Id':'duckdaodime','Symbol':'ddim'},{'Id':'birdchain','Symbol':'bird'},{'Id':'spacechain','Symbol':'spc'},{'Id':'ramp','Symbol':'ramp'},{'Id':'stabilize','Symbol':'stbz'},{'Id':'insured-finance','Symbol':'infi'},{'Id':'crypto-com-chain','Symbol':'cro'},{'Id':'lukso-token','Symbol':'lyxe'},{'Id':'terra-virtua-kolect','Symbol':'tvk'},{'Id':'digg','Symbol':'digg'},{'Id':'freeliquid','Symbol':'fl'},{'Id':'alpha-finance','Symbol':'alpha'},{'Id':'cudos','Symbol':'cudos'},{'Id':'dexe','Symbol':'dexe'},{'Id':'san-diego-coin','Symbol':'sand'},{'Id':'covir','Symbol':'cvr'},{'Id':'typhoon-cash','Symbol':'phoon'},{'Id':'farmer-defi','Symbol':'frm'},{'Id':'polkabridge','Symbol':'pbr'},{'Id':'snowblossom','Symbol':'snow'},{'Id':'tosdis','Symbol':'dis'},{'Id':'poolz-finance','Symbol':'poolz'},{'Id':'zkswap','Symbol':'zks'},{'Id':'armor','Symbol':'armor'},{'Id':'armor-nxm','Symbol':'arnxm'},{'Id':'opium','Symbol':'opium'},{'Id':'yearn-ecosystem-token-index','Symbol':'yeti'},{'Id':'assy-index','Symbol':'assy'},{'Id':'defi-yield-protocol','Symbol':'dyp'},{'Id':'yusdc-busd-pool','Symbol':'yusdc'},{'Id':'veth2','Symbol':'veth2'},{'Id':'aave-eth-v1','Symbol':'aeth'},{'Id':'cream-eth2','Symbol':'creth2'},{'Id':'fantom','Symbol':'ftm'},{'Id':'prosper','Symbol':'pros'},{'Id':'fastswap','Symbol':'fast'},{'Id':'reflect-finance','Symbol':'rfi'},{'Id':'terrausd','Symbol':'ust'},{'Id':'rendoge','Symbol':'rendoge'},{'Id':'mir-coin','Symbol':'mir'},{'Id':'flex-coin','Symbol':'flex'},{'Id':'metric-exchange','Symbol':'metric'},{'Id':'chartex','Symbol':'chart'},{'Id':'bridge-mutual','Symbol':'bmi'},{'Id':'digitex-futures-exchange','Symbol':'dgtx'},{'Id':'millimeter','Symbol':'mm'},{'Id':'tokenlon','Symbol':'lon'},{'Id':'archer-dao-governance-token','Symbol':'arch'},{'Id':'biblepay','Symbol':'bbp'},{'Id':'sx-network','Symbol':'sx'},{'Id':'lattice-token','Symbol':'ltx'},{'Id':'clash-token','Symbol':'sct'},{'Id':'leverj-gluon','Symbol':'l2'},{'Id':'onix','Symbol':'onx'},{'Id':'beefy-finance','Symbol':'bifi'},{'Id':'stafi','Symbol':'fis'},{'Id':'lina','Symbol':'lina'},{'Id':'oin-finance','Symbol':'oin'},{'Id':'xinchb','Symbol':'xINCHb'},{'Id':'xincha','Symbol':'xINCHa'},{'Id':'crowns','Symbol':'cws'},{'Id':'shiba-inu','Symbol':'shib'},{'Id':'portion','Symbol':'prt'},{'Id':'name-changing-token','Symbol':'nct'},{'Id':'muse-2','Symbol':'muse'},{'Id':'maps','Symbol':'maps'},{'Id':'build-finance','Symbol':'build'},{'Id':'gourmetgalaxy','Symbol':'gum'},{'Id':'defi-top-5-tokens-index','Symbol':'defi5'},{'Id':'cryptocurrency-top-10-tokens-index','Symbol':'cc10'},{'Id':'tixl-new','Symbol':'txl'},{'Id':'razor-network','Symbol':'razor'},{'Id':'strudel-finance','Symbol':'trdl'},{'Id':'yvs-finance','Symbol':'yvs'},{'Id':'bundles','Symbol':'bund'},{'Id':'sashimi','Symbol':'sashimi'},{'Id':'hedget','Symbol':'hget'},{'Id':'option-room','Symbol':'room'},{'Id':'wrapped-crescofin','Symbol':'wcres'},{'Id':'gala','Symbol':'gala'},{'Id':'seigniorage-shares','Symbol':'share'},{'Id':'unistake','Symbol':'unistake'},{'Id':'azuki','Symbol':'azuki'},{'Id':'coin-artist','Symbol':'coin'},{'Id':'dextf','Symbol':'dextf'},{'Id':'mp3','Symbol':'mp3'},{'Id':'litentry','Symbol':'lit'},{'Id':'terra-luna','Symbol':'luna'},{'Id':'easyfi','Symbol':'ez'},{'Id':'sync-network','Symbol':'sync'},{'Id':'finxflo','Symbol':'fxf'},{'Id':'bot-ocean','Symbol':'bots'},{'Id':'mar-network','Symbol':'mars'},{'Id':'nftlootbox','Symbol':'loot'},{'Id':'dlp-duck-token','Symbol':'duck'},{'Id':'the-famous-token','Symbol':'tft'},{'Id':'everid','Symbol':'id'},{'Id':'skale','Symbol':'skl'},{'Id':'dao-maker','Symbol':'dao'},{'Id':'bitenium-token','Symbol':'bt'},{'Id':'flash','Symbol':'flash'},{'Id':'butterfly-protocol-2','Symbol':'bfly'},{'Id':'safedot','Symbol':'sdot'},{'Id':'scomp','Symbol':'scomp'},{'Id':'saave','Symbol':'saave'},{'Id':'idot','Symbol':'idot'},{'Id':'soft-yearn','Symbol':'syfi'},{'Id':'suni','Symbol':'suni'},{'Id':'sren','Symbol':'sren'},{'Id':'umbrella-network','Symbol':'umb'},{'Id':'ichi-farm','Symbol':'ichi'},{'Id':'usdp','Symbol':'usdp'},{'Id':'unisocks','Symbol':'socks'},{'Id':'stsla','Symbol':'stsla'},{'Id':'marginswap','Symbol':'mfi'},{'Id':'envion','Symbol':'evn'},{'Id':'klondike-finance','Symbol':'klon'},{'Id':'klondike-btc','Symbol':'kbtc'},{'Id':'open-governance-token','Symbol':'open'},{'Id':'cryptotask-2','Symbol':'ctask'},{'Id':'pylon-finance','Symbol':'pylon'},{'Id':'peanut','Symbol':'nux'},{'Id':'depay','Symbol':'depay'},{'Id':'fyooz','Symbol':'fyz'},{'Id':'scifi-index','Symbol':'scifi'},{'Id':'0chain','Symbol':'zcn'},{'Id':'unicrypt-2','Symbol':'uncx'},{'Id':'warp-finance','Symbol':'warp'},{'Id':'idle','Symbol':'idle'},{'Id':'sparkpoint','Symbol':'srk'},{'Id':'glitch-protocol','Symbol':'glch'},{'Id':'unimex-network','Symbol':'umx'},{'Id':'whiteheart','Symbol':'white'},{'Id':'dent','Symbol':'dent'},{'Id':'zenfuse','Symbol':'zefu'},{'Id':'moontools','Symbol':'moons'},{'Id':'sake-token','Symbol':'sake'},{'Id':'micro-bitcoin-finance','Symbol':'mbtc'},{'Id':'vesper-finance','Symbol':'vsp'},{'Id':'sharedstake-governance-token','Symbol':'sgt'},{'Id':'shroom-finance','Symbol':'shroom'},{'Id':'gameswap-org','Symbol':'gswap'},{'Id':'fudfinance','Symbol':'fud'},{'Id':'rai','Symbol':'rai'},{'Id':'unidex','Symbol':'unidx'},{'Id':'doki-doki-finance','Symbol':'doki'},{'Id':'essentia','Symbol':'ess'},{'Id':'gather','Symbol':'gth'},{'Id':'offshift','Symbol':'xft'},{'Id':'seen','Symbol':'seen'},{'Id':'ethart','Symbol':'arte'},{'Id':'alpaca','Symbol':'alpa'},{'Id':'utu-coin','Symbol':'utu'},{'Id':'achain-coin','Symbol':'ac'},{'Id':'royale','Symbol':'roya'},{'Id':'premia','Symbol':'premia'},{'Id':'rigel-finance','Symbol':'rigel'},{'Id':'poolcoin','Symbol':'pool'},{'Id':'smartcredit-token','Symbol':'smartcredit'},{'Id':'rootkit','Symbol':'root'},{'Id':'revv','Symbol':'revv'},{'Id':'phoenixdao','Symbol':'phnx'},{'Id':'dexkit','Symbol':'kit'},{'Id':'wootrade-network','Symbol':'woo'},{'Id':'modefi','Symbol':'mod'},{'Id':'hydro','Symbol':'hydro'},{'Id':'mask-network','Symbol':'mask'},{'Id':'anyswap','Symbol':'any'},{'Id':'rally-2','Symbol':'rly'},{'Id':'kira-network','Symbol':'kex'},{'Id':'ultra','Symbol':'uos'},{'Id':'geocoin','Symbol':'geo'},{'Id':'get-token','Symbol':'get'},{'Id':'apoyield','Symbol':'soul'},{'Id':'unifi','Symbol':'unifi'},{'Id':'derivadao','Symbol':'ddx'},{'Id':'quick','Symbol':'quick'},{'Id':'redfox-labs-2','Symbol':'rfox'},{'Id':'monacoin','Symbol':'mona'},{'Id':'hybrix','Symbol':'hy'},{'Id':'supercoin','Symbol':'super'},{'Id':'wrapped-dgld','Symbol':'wdgld'},{'Id':'coinshares-gold-and-cryptoassets-index-lite','Symbol':'cgi'},{'Id':'mushroom','Symbol':'mush'},{'Id':'launchpool','Symbol':'lpool'},{'Id':'xtake','Symbol':'xtk'},{'Id':'signal-token','Symbol':'sig'},{'Id':'hopr','Symbol':'hopr'},{'Id':'foundrydao-logistics','Symbol':'fry'},{'Id':'gamecredits','Symbol':'game'},{'Id':'grap-finance','Symbol':'grap'},{'Id':'render-token','Symbol':'rndr'},{'Id':'ovr','Symbol':'ovr'},{'Id':'mettalex','Symbol':'mtlx'},{'Id':'polkamarkets','Symbol':'polk'},{'Id':'bancor-governance-token','Symbol':'vbnt'},{'Id':'nord-finance','Symbol':'nord'},{'Id':'shadows','Symbol':'dows'},{'Id':'mint-club','Symbol':'mint'},{'Id':'degen-index','Symbol':'degen'},{'Id':'bifrost','Symbol':'bfc'},{'Id':'siren','Symbol':'si'},{'Id':'font','Symbol':'font'},{'Id':'moon','Symbol':'moon'},{'Id':'jupiter','Symbol':'jup'},{'Id':'sentiment-token','Symbol':'sent'},{'Id':'dego-finance','Symbol':'dego'},{'Id':'decentral-games','Symbol':'dg'},{'Id':'sota-finance','Symbol':'sota'},{'Id':'noderunners','Symbol':'ndr'},{'Id':'daofi','Symbol':'daofi'},{'Id':'radicle','Symbol':'rad'},{'Id':'alchemix','Symbol':'alcx'},{'Id':'bankless-dao','Symbol':'bank'},{'Id':'antimatter','Symbol':'matter'},{'Id':'fractal','Symbol':'fcl'},{'Id':'verasity','Symbol':'vra'},{'Id':'nft-index','Symbol':'nfti'},{'Id':'bidipass','Symbol':'bdp'},{'Id':'earnscoin','Symbol':'ern'},{'Id':'kylin-network','Symbol':'kyl'},{'Id':'robot','Symbol':'robot'},{'Id':'etha-lend','Symbol':'etha'},{'Id':'paint','Symbol':'paint'},{'Id':'ruler-protocol','Symbol':'ruler'},{'Id':'xfund','Symbol':'xfund'},{'Id':'balpha','Symbol':'balpha'},{'Id':'dea','Symbol':'dea'},{'Id':'chiliz','Symbol':'chz'},{'Id':'inverse-finance','Symbol':'inv'},{'Id':'govi','Symbol':'govi'},{'Id':'bet-protocol','Symbol':'bepro'},{'Id':'non-fungible-yearn','Symbol':'nfy'},{'Id':'blank','Symbol':'blank'},{'Id':'smol','Symbol':'smol'},{'Id':'definitex','Symbol':'dfx'},{'Id':'b20','Symbol':'b20'},{'Id':'tapmydata','Symbol':'tap'},{'Id':'taco-finance','Symbol':'taco'},{'Id':'fyznft','Symbol':'fyznft'},{'Id':'swgtoken','Symbol':'swg'},{'Id':'dusk-network','Symbol':'dusk'},{'Id':'lcx','Symbol':'lcx'},{'Id':'insurace','Symbol':'insur'},{'Id':'tozex','Symbol':'toz'},{'Id':'visor','Symbol':'visr'},{'Id':'aluna','Symbol':'aln'},{'Id':'chain-guardians','Symbol':'cgg'},{'Id':'crust-network','Symbol':'cru'},{'Id':'my-neighbor-alice','Symbol':'alice'},{'Id':'tower','Symbol':'tower'},{'Id':'polyyield-token','Symbol':'yield'},{'Id':'konomi-network','Symbol':'kono'},{'Id':'soar-2','Symbol':'soar'},{'Id':'dovu','Symbol':'dov'},{'Id':'circleex','Symbol':'cx'},{'Id':'juggernaut','Symbol':'jgn'},{'Id':'hoge-finance','Symbol':'hoge'},{'Id':'changenow','Symbol':'now'},{'Id':'connect-financial','Symbol':'cnfi'},{'Id':'hodltree','Symbol':'htre'},{'Id':'polkafoundry','Symbol':'pkf'},{'Id':'exrt-network','Symbol':'exrt'},{'Id':'deri-protocol','Symbol':'deri'},{'Id':'blockchain-cuties-universe-governance','Symbol':'bcug'},{'Id':'labs-group','Symbol':'labs'},{'Id':'kine-protocol','Symbol':'kine'},{'Id':'hapi','Symbol':'hapi'},{'Id':'k21','Symbol':'k21'},{'Id':'union-protocol-governance-token','Symbol':'unn'},{'Id':'habitat','Symbol':'hbt'},{'Id':'cash-tech','Symbol':'cate'},{'Id':'doraemoon','Symbol':'dora'},{'Id':'sifchain','Symbol':'erowan'},{'Id':'sentivate','Symbol':'sntvt'},{'Id':'chain-games','Symbol':'chain'},{'Id':'xdefi-governance-token','Symbol':'xdex'},{'Id':'upbots','Symbol':'ubxt'},{'Id':'graphlinq-protocol','Symbol':'glq'},{'Id':'lympo','Symbol':'lym'},{'Id':'vidya','Symbol':'vidya'},{'Id':'fireball','Symbol':'fire'},{'Id':'dafi-protocol','Symbol':'dafi'},{'Id':'oddz','Symbol':'oddz'},{'Id':'paypolitan-token','Symbol':'epan'},{'Id':'ara-token','Symbol':'ara'},{'Id':'2gether-2','Symbol':'2gt'},{'Id':'venus-eth','Symbol':'veth'},{'Id':'coinfirm-amlt','Symbol':'amlt'},{'Id':'volentix-vtx','Symbol':'vtx'},{'Id':'vvsp','Symbol':'vvsp'},{'Id':'tribe-2','Symbol':'tribe'},{'Id':'fei-protocol','Symbol':'fei'},{'Id':'xsgd','Symbol':'xsgd'},{'Id':'aioz-network','Symbol':'aioz'},{'Id':'spheroid-universe','Symbol':'sph'},{'Id':'pocmon','Symbol':'pmon'},{'Id':'sylo','Symbol':'sylo'},{'Id':'overline-emblem','Symbol':'emb'},{'Id':'ureeqa','Symbol':'urqa'},{'Id':'linkpool','Symbol':'lpl'},{'Id':'curate','Symbol':'xcur'},{'Id':'cook','Symbol':'cook'},{'Id':'cellframe','Symbol':'cell'},{'Id':'mad-network','Symbol':'mad'},{'Id':'convergence','Symbol':'conv'},{'Id':'swarm','Symbol':'swm'},{'Id':'eddaswap','Symbol':'edda'},{'Id':'tidal-finance','Symbol':'tidal'},{'Id':'deracoin','Symbol':'drc'},{'Id':'xyo-network','Symbol':'xyo'},{'Id':'arcona','Symbol':'arcona'},{'Id':'vulcan-forged','Symbol':'pyr'},{'Id':'roobee','Symbol':'roobee'},{'Id':'deeper-network','Symbol':'dpr'},{'Id':'gains','Symbol':'gains'},{'Id':'liquity-usd','Symbol':'lusd'},{'Id':'equalizer','Symbol':'eqz'},{'Id':'genesis-shards','Symbol':'gs'},{'Id':'internxt','Symbol':'inxt'},{'Id':'olympus','Symbol':'ohm'},{'Id':'raze-network','Symbol':'raze'},{'Id':'alchemist','Symbol':'mist'},{'Id':'cardstarter','Symbol':'cards'},{'Id':'ethbox-token','Symbol':'ebox'},{'Id':'presearch','Symbol':'pre'},{'Id':'ethereum-push-notification-service','Symbol':'push'},{'Id':'zoracles','Symbol':'zora'},{'Id':'boson-protocol','Symbol':'boson'},{'Id':'universal-basic-income','Symbol':'ubi'},{'Id':'total-crypto-market-cap-token','Symbol':'tcap'},{'Id':'basketdao','Symbol':'bask'},{'Id':'nkn','Symbol':'nkn'},{'Id':'the-4th-pillar','Symbol':'four'},{'Id':'dentacoin','Symbol':'dcn'},{'Id':'ampleforth-governance-token','Symbol':'forth'},{'Id':'s1inch','Symbol':'s1inch'},{'Id':'srune','Symbol':'srune'},{'Id':'scrv','Symbol':'scrv'},{'Id':'snflx','Symbol':'snflx'},{'Id':'sfb','Symbol':'sfb'},{'Id':'sgoog','Symbol':'sgoog'},{'Id':'samzn','Symbol':'samzn'},{'Id':'blind-boxes','Symbol':'bles'},{'Id':'stakewise','Symbol':'swise'},{'Id':'kyber-network-crystal','Symbol':'knc'},{'Id':'yaxis','Symbol':'yaxis'},{'Id':'orbs','Symbol':'orbs'},{'Id':'wirex','Symbol':'wxt'},{'Id':'shincoin','Symbol':'scoin'},{'Id':'baguette','Symbol':'bag'},{'Id':'occamfi','Symbol':'occ'},{'Id':'illuvium','Symbol':'ilv'},{'Id':'unfederalreserve','Symbol':'ersdl'},{'Id':'ice-token','Symbol':'ice'},{'Id':'xend-finance','Symbol':'xend'},{'Id':'unmarshal','Symbol':'marsh'},{'Id':'aga-token','Symbol':'aga'},{'Id':'circuits-of-value','Symbol':'coval'},{'Id':'tenset','Symbol':'10set'},{'Id':'bonfi','Symbol':'bnf'},{'Id':'kin','Symbol':'kin'},{'Id':'golem','Symbol':'glm'},{'Id':'telcoin','Symbol':'tel'},{'Id':'unlock-protocol','Symbol':'udt'},{'Id':'pendle','Symbol':'pendle'},{'Id':'waxe','Symbol':'waxe'},{'Id':'coinstarter','Symbol':'stc'},{'Id':'route','Symbol':'route'},{'Id':'nahmii','Symbol':'nii'},{'Id':'paid-network','Symbol':'paid'},{'Id':'keytango','Symbol':'tango'},{'Id':'splyt','Symbol':'shopx'},{'Id':'ares-protocol','Symbol':'ares'},{'Id':'aga-rewards-2','Symbol':'agar'},{'Id':'cryptex-finance','Symbol':'ctx'},{'Id':'shih-tzu','Symbol':'shih'},{'Id':'somidax','Symbol':'smdx'},{'Id':'kishu-inu','Symbol':'kishu'},{'Id':'feg-token','Symbol':'feg'},{'Id':'stobox-token','Symbol':'stbu'},{'Id':'o3-swap','Symbol':'o3'},{'Id':'woofy','Symbol':'woofy'},{'Id':'shibaken-finance','Symbol':'shibaken'},{'Id':'kirobo','Symbol':'kiro'},{'Id':'convex-finance','Symbol':'cvx'},{'Id':'seedswap','Symbol':'snft'},{'Id':'8pay','Symbol':'8pay'},{'Id':'game','Symbol':'gtc'},{'Id':'graviton','Symbol':'gton'},{'Id':'alchemix-usd','Symbol':'alusd'},{'Id':'sarcophagus','Symbol':'sarco'},{'Id':'terablock','Symbol':'tbc'},{'Id':'value-usd','Symbol':'vusd'},{'Id':'hokkaidu-inu','Symbol':'hokk'},{'Id':'boringdao-[old]','Symbol':'bor'},{'Id':'zoo-token','Symbol':'zoot'},{'Id':'dogelon-mars','Symbol':'elon'},{'Id':'superbid','Symbol':'superbid'},{'Id':'nft-tone','Symbol':'tone'},{'Id':'smartkey','Symbol':'skey'},{'Id':'nimbus','Symbol':'nbu'},{'Id':'leash','Symbol':'leash'},{'Id':'district0x','Symbol':'dnt'},{'Id':'defi-factory-token','Symbol':'deft'},{'Id':'dfyn-network','Symbol':'dfyn'},{'Id':'metaverse-index','Symbol':'mvi'},{'Id':'akita-inu','Symbol':'akita'},{'Id':'liquity','Symbol':'lqty'},{'Id':'verox','Symbol':'vrx'},{'Id':'baby-bitcoin','Symbol':'bbtc'},{'Id':'munch-token','Symbol':'munch'},{'Id':'bezoge-earth','Symbol':'bezoge'},{'Id':'island-coin','Symbol':'isle'},{'Id':'bitcashpay','Symbol':'bcp'},{'Id':'ethereummax','Symbol':'emax'},{'Id':'instadapp','Symbol':'inst'},{'Id':'cavapoo','Symbol':'cava'},{'Id':'swapp','Symbol':'swapp'},{'Id':'dvision-network','Symbol':'dvi'},{'Id':'cad-coin','Symbol':'cadc'},{'Id':'arc-governance','Symbol':'arcx'},{'Id':'amun-defi-index','Symbol':'dfi'},{'Id':'amun-defi-momentum-index','Symbol':'dmx'},{'Id':'xsushi','Symbol':'xsushi'},{'Id':'nxm','Symbol':'nxm'},{'Id':'unit-protocol','Symbol':'col'},{'Id':'auction','Symbol':'auction'},{'Id':'singularitynet','Symbol':'agix'},{'Id':'olyseum','Symbol':'oly'},{'Id':'unizen','Symbol':'zcx'},{'Id':'fnkcom','Symbol':'fnk'},{'Id':'gerowallet','Symbol':'gero'},{'Id':'unobtanium','Symbol':'uno', 'Id': 'gmx', 'Symbol': 'GMX'}, {'Id':'usdc', 'Symbol':'USDC'}]"; + + public static List GetTokens() + { + return JsonConvert.DeserializeObject(TokensJSON).Tokens; + } + + public static List GetGeckoTokens() + { + return JsonConvert.DeserializeObject>(GeckoJson); + } + + public static GeckoToken GetGeckoToken(string ticker) + { + var geckoToken = GetGeckoTokens(); + return geckoToken.FirstOrDefault(t => string.Equals(t.Symbol, ticker, StringComparison.CurrentCultureIgnoreCase)); + } + + public static string GetContractAddress(Ticker ticker) => ticker switch + { + Ticker.BTC => Arbitrum.Address.WBTC, + Ticker.ETH => Arbitrum.Address.ETH, + Ticker.LINK => Arbitrum.Address.LINK, + Ticker.UNI => Arbitrum.Address.UNI, + Ticker.GMX => Arbitrum.Address.GMX, + Ticker.USDT => Arbitrum.Address.USDT, + Ticker.USDC => Arbitrum.Address.USDC, + _ => Arbitrum.Address.Zero + }; + + public static int GetDecimal(Ticker ticker) => ticker switch + { + Ticker.BTC => 8, + Ticker.ETH => 18, + Ticker.LINK => 18, + Ticker.UNI => 18, + Ticker.GMX => 18, + Ticker.USDT => 6, + Ticker.USDC => 6, + _ => throw new Exception($"No decimal for {ticker}") + }; + + public static Ticker GetTicker(string address) => address.ToLowerInvariant() switch + { + Arbitrum.Address.WBTC => Ticker.BTC, + Arbitrum.Address.ETH => Ticker.ETH, + Arbitrum.Address.LINK => Ticker.LINK, + Arbitrum.Address.UNI => Ticker.UNI, + Arbitrum.Address.GMX => Ticker.GMX, + Arbitrum.Address.USDT => Ticker.USDT, + Arbitrum.Address.USDC => Ticker.USDC, + _ => throw new NotImplementedException(), + }; +} diff --git a/src/Managing.Infrastructure.Web3/Services/TradaoService.cs b/src/Managing.Infrastructure.Web3/Services/TradaoService.cs new file mode 100644 index 0000000..6d8e0a6 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Services/TradaoService.cs @@ -0,0 +1,98 @@ +using Managing.Application.Abstractions.Services; +using Managing.Domain.Statistics; +using Managing.Domain.Trades; +using Managing.Infrastructure.Evm.Models; +using System.Net.Http.Json; + +namespace Managing.Infrastructure.Evm.Services; + +public class TradaoService : ITradaoService +{ + private readonly HttpClient _httpClient; + + public TradaoService() + { + _httpClient = new HttpClient(); ; + } + + public async Task> GetBadTrader() + { + var bestTraders = await _httpClient.GetFromJsonAsync($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/asc/2592000/0/?current=1&limit=500&order=asc&window=2592000&chain=42161&exchange=gmx&openPosition=0"); + + if (bestTraders == null || bestTraders.row.Count == 0) + { + return new List(); + } + + return await GetTraderDetails(bestTraders); + } + + + public async Task> GetBestTrader() + { + var bestTraders = await _httpClient.GetFromJsonAsync($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/desc/2592000/0/?current=1&limit=500&order=desc&window=2592000&chain=42161&exchange=gmx&openPosition=0"); + + if (bestTraders == null || bestTraders.row.Count == 0) + { + return new List(); + } + + return await GetTraderDetails(bestTraders); + } + + public async Task> GetTrades(string address) + { + var response = await _httpClient.GetFromJsonAsync($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{address}"); + + var trades = new List(); + + if (response == null) return trades; + + foreach (var position in response.openPositions) + { + var trade = new Trade( + DateTime.UtcNow, + position.isLong ? Common.Enums.TradeDirection.Long : Common.Enums.TradeDirection.Short, + Common.Enums.TradeStatus.Filled, + Common.Enums.TradeType.Market, + TokenService.GetTicker(position.indexTokenAddress), + Convert.ToDecimal(position.collateral), + Convert.ToDecimal(position.averagePrice), + Convert.ToDecimal(position.position) / Convert.ToDecimal(position.collateral), + address, position.liqPrice + ); + + trades.Add(trade); + } + + return trades; + } + + private async Task> GetTraderDetails(TradaoList traders) + { + var result = new List(); + foreach (var trader in traders.row) + { + var response = await _httpClient.GetFromJsonAsync($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{trader.user}"); + + if (response != null) + result.Add(Map(response, trader.user)); + } + + return result; + } + + private Trader Map(TradaoUserDetails response, string address) + { + return new Trader + { + Address = address, + Winrate = (int)(response.summary.winRate * 100), + Pnl = Convert.ToDecimal(response.summary.pnl), + TradeCount = response.summary.trades, + AverageWin = Convert.ToDecimal(response.summary.averageWin), + AverageLoss = Convert.ToDecimal(response.summary.averageLoss), + Roi = Convert.ToDecimal(response.summary.roi) + }; + } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs new file mode 100644 index 0000000..58a4baf --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs @@ -0,0 +1,122 @@ +using GraphQL.Client.Abstractions; +using GraphQL; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Core; +using Managing.Infrastructure.Evm.Subgraphs.Models; +using Managing.Common; +using Managing.Domain.Candles; +using Managing.Infrastructure.Evm.Extensions; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Subgraphs; + +public class Chainlink : ISubgraphPrices +{ + SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.ChainlinkPrice; + + private readonly IGraphQLClient _graphQLClient; + private readonly string _baseToken = "/USD"; + + public Chainlink(IGraphQLClient graphQLHttpClient) + { + _graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient)); + } + + public async Task> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var path = ticker.ToString() + _baseToken; + var batchSize = 1000; + var batchMax = 6; + var priceRounds = new List(); + var feedCondition = $@"{{ assetPair: ""{path}"" }}"; + + // Fetching prices from graphql ticker + for (int i = 0; i < batchMax; i++) + { + var query = $"{{ prices(first: {batchSize}, skip: {i * batchSize}, orderBy: timestamp, orderDirection: desc, where: {feedCondition} ) {{ timestamp,price}} }}"; + var graphQuery = new GraphQLRequest + { + Query = query + }; + + var response = await _graphQLClient.SendQueryAsync(graphQuery); + priceRounds.AddRange(response.Data.Prices); + } + + var rounds = new List(); + + // Format response + foreach (var round in priceRounds) + { + var timestamp = int.Parse(round.Timestamp); + rounds.Add(new Round + { + UnixTimestamp = timestamp, + Value = (double.Parse(round.Price) / 1e8).ToString(), + Date = DateHelpers.GetFromUnixTimestamp(timestamp) + }); + } + + rounds.Sort((timeA, timeB) => timeA.UnixTimestamp - timeB.UnixTimestamp); + + return rounds.ToArray().GetCandles(timeframe, ticker); + } + + public Task GetVolume(Ticker ticker) + { + //var query = $"{{ assetPairs() {{ id }} }}"; + //var graphQuery = new GraphQLRequest + //{ + // Query = query + //}; + + //var response = await _graphQLClient.SendQueryAsync(graphQuery); + throw new NotImplementedException(); + } + + public async Task> GetTickers() + { + var batchSize = 100; + var batchMax = 10; + var tickers = new List(); + + for (int i = 0; i < batchMax; i++) + { + var query = $"{{ assetPairs(first: {batchSize}, skip: {i * batchSize}) {{ id }} }}"; + var graphQuery = new GraphQLRequest + { + Query = query + }; + + var response = await _graphQLClient.SendQueryAsync(graphQuery); + + if (response.Data?.AssetPairs != null) + { + tickers.AddRange(ParseTickers(response.Data.AssetPairs)); + } + } + + return tickers; + } + + private List ParseTickers(List pairs) + { + var tickers = new List(); + foreach (var pair in pairs) + { + var items = pair.Id.Split('/'); + + if (items.Length == 2 && items[1] == Constants.Stablecoins.Usd) + { + try + { + var ticker = MiscExtensions.ParseEnum(items[0]); + tickers.Add(ticker); + } + catch (Exception ex) { } + } + } + + return tickers; + } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs b/src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs new file mode 100644 index 0000000..5d8c7d0 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs @@ -0,0 +1,92 @@ +using GraphQL.Client.Abstractions; +using GraphQL; +using Managing.Core; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Infrastructure.Evm.Subgraphs.Models; +using Managing.Domain.Candles; +using Managing.Infrastructure.Evm.Extensions; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Subgraphs; + +public class ChainlinkGmx : ISubgraphPrices +{ + SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.ChainlinkGmx; + + private readonly IGraphQLClient _graphQLClient; + private readonly string _baseToken = "_USD"; + private Dictionary _feeds = new Dictionary() + { + {"BTC_USD", "0xae74faa92cb67a95ebcab07358bc222e33a34da7" }, + {"ETH_USD", "0x37bc7498f4ff12c19678ee8fe19d713b87f6a9e6" }, + {"BNB_USD", "0xc45ebd0f901ba6b2b8c7e70b717778f055ef5e6d" }, + {"LINK_USD", "0xdfd03bfc3465107ce570a0397b247f546a42d0fa" }, + {"UNI_USD", "0x68577f915131087199fe48913d8b416b3984fd38" }, + {"SUSHI_USD", "0x7213536a36094cd8a768a5e45203ec286cba2d74" }, + {"AVAX_USD", "0x0fc3657899693648bba4dbd2d8b33b82e875105d" }, + {"AAVE_USD", "0xe3f0dede4b499c07e12475087ab1a084b5f93bc0" }, + {"YFI_USD", "0x8a4d74003870064d41d4f84940550911fbfccf04" }, + {"SPELL_USD", "0x8640b23468815902e011948f3ab173e1e83f9879" }, + }; + + public ChainlinkGmx(IGraphQLClient graphQLHttpClient) + { + _graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient)); + } + + + public async Task> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var path = ticker.ToString() + _baseToken; + var feed = _feeds.GetValueOrDefault(path); + var perChunk = 1000; + var totalChunk = 6; + var priceRounds = new List(); + var feedCondition = $@"{{ feed: ""{feed}"" }}"; + + for (int i = 0; i < totalChunk; i++) + { + var query = $"{{ rounds(first: {perChunk}, skip: {i * perChunk}, orderBy: unixTimestamp, orderDirection: desc, where: {feedCondition} ) {{ unixTimestamp,value}} }}"; + var graphQuery = new GraphQLRequest + { + Query = query + }; + + var response = await _graphQLClient.SendQueryAsync(graphQuery); + priceRounds.AddRange(response.Data.Rounds); + } + + var rounds = new List(); + var uniqTs = new HashSet(); + + foreach (var round in priceRounds) + { + if (uniqTs.Contains(round.UnixTimestamp)) + { + continue; + } + + uniqTs.Add(round.UnixTimestamp); + rounds.Add(new Round + { + UnixTimestamp = round.UnixTimestamp, + Value = (double.Parse(round.Value) / 1e8).ToString(), + Date = DateHelpers.GetFromUnixTimestamp(round.UnixTimestamp) + }); + } + + rounds.Sort((timeA, timeB) => timeA.UnixTimestamp - timeB.UnixTimestamp); + + return rounds.ToArray().GetCandles(timeframe, ticker); + } + + public Task GetVolume(Ticker ticker) + { + throw new NotImplementedException(); + } + + public Task> GetTickers() + { + throw new NotImplementedException(); + } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs new file mode 100644 index 0000000..f726eb4 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs @@ -0,0 +1,104 @@ +using GraphQL; +using GraphQL.Client.Abstractions; +using Managing.Core; +using Managing.Domain.Candles; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Infrastructure.Evm.Services; +using Managing.Infrastructure.Evm.Subgraphs.Models; +using NBitcoin; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Subgraphs; + +public class Gbc : ISubgraphPrices +{ + private readonly IGraphQLClient _graphQLClient; + public SubgraphProvider GetProvider() => SubgraphProvider.Gbc; + + public Gbc(IGraphQLClient graphQLHttpClient) + { + _graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient)); + } + + public async Task> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe) + { + var batchSize = 1000; + var batchMax = 6; + var priceRounds = new List(); + var tickerContract = TokenService.GetContractAddress(ticker); + var unixTimeframe = timeframe.GetUnixInterval(); + var start = startDate.ToUnixTimestamp(); + var end = DateTime.UtcNow.ToUnixTimestamp(); + var feedCondition = $@"{{ tokenAddress: ""_{tickerContract}"", interval: ""_{unixTimeframe}"", timestamp_gte: {start}, timestamp_lte: {end} }}"; + + // Fetching prices from graphql ticker + for (int i = 0; i < batchMax; i++) + { + var query = $"{{ pricefeeds(first: {batchSize}, skip: {i * batchSize}, orderBy: timestamp, orderDirection: desc, where: {feedCondition} ) {{ timestamp,o,h,l,c}} }}"; + var graphQuery = new GraphQLRequest + { + Query = query + }; + + var response = await _graphQLClient.SendQueryAsync(graphQuery); + priceRounds.AddRange(response.Data.PriceFeeds); + } + + priceRounds.Sort((timeA, timeB) => timeA.Timestamp - timeB.Timestamp); + + var candles = new List(); + + var firstRound = priceRounds.FirstOrDefault(); + if (firstRound == null) + return candles; + + var previousCandle = BuildCandle(firstRound, ticker, timeframe); + + // Format response + foreach (var price in priceRounds.Skip(1)) + { + var candle = BuildCandle(price, ticker, timeframe); + candle.OpenTime = previousCandle.Date; + candles.Add(candle); + } + + return candles; + } + + private Candle BuildCandle(GbcPrice ohlc, Ticker ticker, Timeframe timeframe) + { + return new Candle() + { + Date = DateHelpers.GetFromUnixTimestamp(ohlc.Timestamp), + Open = FormatPrice(ohlc.O), + High = FormatPrice(ohlc.H), + Low = FormatPrice(ohlc.L), + Close = FormatPrice(ohlc.C), + Exchange = TradingExchanges.Evm, + Ticker = ticker.ToString(), + Timeframe = timeframe + }; + } + + private static decimal FormatPrice(string price) + { + return (decimal)(double.Parse(price) / 1e30); + } + + public Task GetVolume(Ticker ticker) + { + throw new NotImplementedException(); + } + + public Task> GetTickers() + { + var tickers = new List() { + Ticker.BTC, + Ticker.LINK, + Ticker.ETH, + Ticker.UNI + }; + + return Task.FromResult(tickers.AsEnumerable()); + } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/GbcPrices.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/GbcPrices.cs new file mode 100644 index 0000000..f5008ce --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/GbcPrices.cs @@ -0,0 +1,15 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class GbcPrices +{ + public List PriceFeeds { get; set; } +} + +public class GbcPrice +{ + public int Timestamp { get; set; } + public string O { get; set; } + public string H { get; set; } + public string L { get; set; } + public string C { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs new file mode 100644 index 0000000..60f52d7 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs @@ -0,0 +1,11 @@ +using Nethereum.Contracts.Standards.ERC20.TokenList; + +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class Pair +{ + public string ReserveETH { get; set; } + public string ReserveUSD { get; set; } + public Token Token0 { get; set; } + public Token Token1 { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pools.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pools.cs new file mode 100644 index 0000000..5bc5fd2 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Pools.cs @@ -0,0 +1,6 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class Pools +{ + public List Pairs { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs new file mode 100644 index 0000000..973b344 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs @@ -0,0 +1,17 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class ChainlinkPrice +{ + public string Price { get; set; } + public string Timestamp { get; set; } +} + +public class ChainlinkAssetPairs +{ + public List AssetPairs { get; set; } +} + +public class AssetPair +{ + public string Id { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/PricesChainlink.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/PricesChainlink.cs new file mode 100644 index 0000000..9117571 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/PricesChainlink.cs @@ -0,0 +1,6 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class ChainlinkPrices +{ + public List Prices { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/Round.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Round.cs new file mode 100644 index 0000000..d90063c --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Round.cs @@ -0,0 +1,8 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class Round +{ + public int UnixTimestamp { get; set; } + public DateTime Date { get; set; } + public string Value { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/Rounds.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Rounds.cs new file mode 100644 index 0000000..93db593 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/Rounds.cs @@ -0,0 +1,6 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class PriceRoundsChainlinkGmx +{ + public List Rounds { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/TokenDetails.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/TokenDetails.cs new file mode 100644 index 0000000..62a7636 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/TokenDetails.cs @@ -0,0 +1,20 @@ +using Nethereum.Contracts.Standards.ERC20.TokenList; +using System.Text.Json.Serialization; + +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class TokenDetails : Token +{ + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string TradeVolume { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string TradeVolumeUSD { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string TotalSupply { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string TotalLiquidity { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Models/TopTokens.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Models/TopTokens.cs new file mode 100644 index 0000000..6c84639 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Models/TopTokens.cs @@ -0,0 +1,6 @@ +namespace Managing.Infrastructure.Evm.Subgraphs.Models; + +public class TopTokens +{ + public List Tokens { get; set; } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/SubgraphExtensions.cs b/src/Managing.Infrastructure.Web3/Subgraphs/SubgraphExtensions.cs new file mode 100644 index 0000000..f179275 --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/SubgraphExtensions.cs @@ -0,0 +1,41 @@ +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Infrastructure.Evm.Services; +using Microsoft.Extensions.DependencyInjection; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Subgraphs; + +public static class SubgraphExtensions +{ + public static void AddUniswapV2(this IServiceCollection services) + { + services.AddSingleton(ctx => + { + return new Uniswap(SubgraphService.GetSubgraphClient(SubgraphProvider.UniswapV2)); + }); + } + + public static void AddChainlink(this IServiceCollection services) + { + services.AddSingleton(ctx => + { + return new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice)); + }); + } + + public static void AddGbcFeed(this IServiceCollection services) + { + services.AddSingleton(ctx => + { + return new Gbc(SubgraphService.GetSubgraphClient(SubgraphProvider.Gbc)); + }); + } + + public static void AddChainlinkGmx(this IServiceCollection services) + { + services.AddSingleton(ctx => + { + return new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx)); + }); + } +} diff --git a/src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs b/src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs new file mode 100644 index 0000000..5f78f5a --- /dev/null +++ b/src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs @@ -0,0 +1,85 @@ +using GraphQL.Client.Abstractions; +using GraphQL; +using Managing.Infrastructure.Evm.Abstractions; +using Managing.Infrastructure.Evm.Subgraphs.Models; +using Managing.Domain.Candles; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Evm.Subgraphs; + +public class Uniswap : IUniswap +{ + SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.UniswapV2; + + private readonly IGraphQLClient _graphQLClient; + + public Uniswap(IGraphQLClient graphQLHttpClient) + { + _graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient)); + } + + /// + /// Get the first 150 most liquid market pairs ordered by desc + /// + /// + public async Task GetMostLiquidMarketPairs() + { + var query = new GraphQLRequest + { + Query = @" + { + pairs(first: 150, orderBy: reserveETH orderDirection: desc){ + token0 { + symbol + } + token1 { + symbol + } + reserveETH + reserveUSD + } + } + " + }; + + GraphQLResponse response = await _graphQLClient.SendQueryAsync(query); + return response.Data; + } + + public async Task GetTopTokens() + { + var query = new GraphQLRequest + { + Query = @" + { + tokens (first: 150, orderBy: tradeVolumeUSD orderDirection: desc){ + symbol + name + tradeVolume + tradeVolumeUSD + totalSupply + totalLiquidity + } + } + " + }; + + GraphQLResponse response = await _graphQLClient.SendQueryAsync(query); + return response.Data; + } + + public Task> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe) + { + throw new NotImplementedException(); + } + + public Task GetVolume(Ticker ticker) + { + throw new NotImplementedException(); + } + + public Task> GetTickers() + { + throw new NotImplementedException(); + } +} diff --git a/src/Managing.Infrastructure.Worker/Managing.Infrastructure.Worker.csproj b/src/Managing.Infrastructure.Worker/Managing.Infrastructure.Worker.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/src/Managing.Infrastructure.Worker/Managing.Infrastructure.Worker.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/Managing.Infrastructure.Worker/WorkerService.cs b/src/Managing.Infrastructure.Worker/WorkerService.cs new file mode 100644 index 0000000..768a69a --- /dev/null +++ b/src/Managing.Infrastructure.Worker/WorkerService.cs @@ -0,0 +1,8 @@ +using System; + +namespace Managing.Infrastructure.Worker +{ + public class Class1 + { + } +} diff --git a/src/Managing.Tools.ABI/Gmx/core/OrderBook.abi b/src/Managing.Tools.ABI/Gmx/core/OrderBook.abi new file mode 100644 index 0000000..d354d58 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/OrderBook.abi @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CancelDecreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"purchaseToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"purchaseTokenAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CancelIncreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CancelSwapOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CreateDecreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"purchaseToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"purchaseTokenAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CreateIncreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"CreateSwapOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionPrice","type":"uint256"}],"name":"ExecuteDecreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"purchaseToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"purchaseTokenAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionPrice","type":"uint256"}],"name":"ExecuteIncreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"ExecuteSwapOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"router","type":"address"},{"indexed":false,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"address","name":"weth","type":"address"},{"indexed":false,"internalType":"address","name":"usdg","type":"address"},{"indexed":false,"internalType":"uint256","name":"minExecutionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minPurchaseTokenAmountUsd","type":"uint256"}],"name":"Initialize","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"}],"name":"UpdateDecreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"gov","type":"address"}],"name":"UpdateGov","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"orderIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"}],"name":"UpdateIncreaseOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minExecutionFee","type":"uint256"}],"name":"UpdateMinExecutionFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minPurchaseTokenAmountUsd","type":"uint256"}],"name":"UpdateMinPurchaseTokenAmountUsd","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"ordexIndex","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"indexed":false,"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"indexed":false,"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"}],"name":"UpdateSwapOrder","type":"event"},{"inputs":[],"name":"PRICE_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"USDG_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"cancelDecreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"cancelIncreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_swapOrderIndexes","type":"uint256[]"},{"internalType":"uint256[]","name":"_increaseOrderIndexes","type":"uint256[]"},{"internalType":"uint256[]","name":"_decreaseOrderIndexes","type":"uint256[]"}],"name":"cancelMultiple","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"cancelSwapOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_triggerPrice","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"}],"name":"createDecreaseOrder","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_triggerPrice","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"_executionFee","type":"uint256"},{"internalType":"bool","name":"_shouldWrap","type":"bool"}],"name":"createIncreaseOrder","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_triggerRatio","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"_executionFee","type":"uint256"},{"internalType":"bool","name":"_shouldWrap","type":"bool"},{"internalType":"bool","name":"_shouldUnwrap","type":"bool"}],"name":"createSwapOrder","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"decreaseOrders","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"decreaseOrdersIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"address payable","name":"_feeReceiver","type":"address"}],"name":"executeDecreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"address payable","name":"_feeReceiver","type":"address"}],"name":"executeIncreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"address payable","name":"_feeReceiver","type":"address"}],"name":"executeSwapOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"getDecreaseOrder","outputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"getIncreaseOrder","outputs":[{"internalType":"address","name":"purchaseToken","type":"address"},{"internalType":"uint256","name":"purchaseTokenAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_orderIndex","type":"uint256"}],"name":"getSwapOrder","outputs":[{"internalType":"address","name":"path0","type":"address"},{"internalType":"address","name":"path1","type":"address"},{"internalType":"address","name":"path2","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_otherToken","type":"address"}],"name":"getUsdgMinPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gov","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"increaseOrders","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"purchaseToken","type":"address"},{"internalType":"uint256","name":"purchaseTokenAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint256","name":"triggerPrice","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"increaseOrdersIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_router","type":"address"},{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_usdg","type":"address"},{"internalType":"uint256","name":"_minExecutionFee","type":"uint256"},{"internalType":"uint256","name":"_minPurchaseTokenAmountUsd","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minExecutionFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minPurchaseTokenAmountUsd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gov","type":"address"}],"name":"setGov","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minExecutionFee","type":"uint256"}],"name":"setMinExecutionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minPurchaseTokenAmountUsd","type":"uint256"}],"name":"setMinPurchaseTokenAmountUsd","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"swapOrders","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"triggerRatio","type":"uint256"},{"internalType":"bool","name":"triggerAboveThreshold","type":"bool"},{"internalType":"bool","name":"shouldUnwrap","type":"bool"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"swapOrdersIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"uint256","name":"_triggerPrice","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"}],"name":"updateDecreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"uint256","name":"_triggerPrice","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"}],"name":"updateIncreaseOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_orderIndex","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_triggerRatio","type":"uint256"},{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"}],"name":"updateSwapOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"usdg","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_triggerAboveThreshold","type":"bool"},{"internalType":"uint256","name":"_triggerPrice","type":"uint256"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"bool","name":"_maximizePrice","type":"bool"},{"internalType":"bool","name":"_raise","type":"bool"}],"name":"validatePositionOrderPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_triggerRatio","type":"uint256"}],"name":"validateSwapOrderPriceWithTriggerAboveThreshold","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/OrderBook.bin b/src/Managing.Tools.ABI/Gmx/core/OrderBook.bin new file mode 100644 index 0000000..b433565 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/OrderBook.bin @@ -0,0 +1 @@ +6080604052600e805460ff1916905534801561001a57600080fd5b506001600055600780546001600160a01b031916331790556153c4806100416000396000f3fe6080604052600436106101c35760003560e01c8062cf066b1461022a578063026032ee1461026f57806307c7edc3146102fa5780630d5cc9381461033d57806311d9444a1461036757806312d43a51146103aa578063269ae6c2146103db5780632b7d6290146104a6578063392e53cd146105465780633fc8cef31461056f57806347e0bbd0146105845780634a686d67146105ae5780634c54f0b0146105c357806363ae21031461062d57806379221fa214610642578063807c5600146106c15780638de10c2e1461087357806395082d25146108885780639983ee1b1461089d5780639e23de5c146108db5780639e71b0f01461090e578063a397ea5414610938578063aec224551461097c578063b142a4b0146109af578063c16cde8a14610a99578063c4a1821b14610ae9578063c86b0f7d14610b99578063cfad57a214610bd7578063d0d40cd614610c0a578063d38ab51914610c9d578063d3bab1d114610ce0578063d566d0ca14610d74578063d7c41c7914610da7578063f2d2e01b14610e00578063f5b91b7b14610e94578063f882ac0714610ea9578063f887ea4014610ed3578063fbfa77cf14610ee8578063fc2cee6214610efd57610225565b36610225576008546001600160a01b03163314610223576040805162461bcd60e51b815260206004820152601960248201527827b93232b92137b7b59d1034b73b30b634b21039b2b73232b960391b604482015290519081900360640190fd5b005b600080fd5b34801561023657600080fd5b5061025d6004803603602081101561024d57600080fd5b50356001600160a01b0316610f27565b60408051918252519081900360200190f35b34801561027b57600080fd5b506102a86004803603604081101561029257600080fd5b506001600160a01b038135169060200135610f39565b604080516001600160a01b03998a168152602081019890985295909716868601526060860193909352901515608085015260a0840152151560c083015260e08201929092529051908190036101000190f35b34801561030657600080fd5b506102236004803603606081101561031d57600080fd5b506001600160a01b0381358116916020810135916040909101351661100c565b34801561034957600080fd5b506102236004803603602081101561036057600080fd5b5035611417565b34801561037357600080fd5b506102236004803603606081101561038a57600080fd5b506001600160a01b038135811691602081013591604090910135166114a8565b3480156103b657600080fd5b506103bf611857565b604080516001600160a01b039092168252519081900360200190f35b61022360048036036101008110156103f257600080fd5b810190602081018135600160201b81111561040c57600080fd5b82018360208201111561041e57600080fd5b803590602001918460208302840111600160201b8311171561043f57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604081013590606081013515159060808101359060a081013515159060c001351515611866565b3480156104b257600080fd5b506104df600480360360408110156104c957600080fd5b506001600160a01b038135169060200135611bef565b604080516001600160a01b039b8c168152998b1660208b015289810198909852958916606089015293909716608087015260a0860191909152151560c085015260e08401949094529215156101008301526101208201929092529051908190036101400190f35b34801561055257600080fd5b5061055b611c61565b604080519115158252519081900360200190f35b34801561057b57600080fd5b506103bf611c6a565b34801561059057600080fd5b50610223600480360360208110156105a757600080fd5b5035611c79565b3480156105ba57600080fd5b5061025d611f83565b3480156105cf57600080fd5b50610614600480360360a08110156105e657600080fd5b5080351515906020810135906001600160a01b03604082013516906060810135151590608001351515611f8f565b6040805192835290151560208301528051918290030190f35b34801561063957600080fd5b5061025d6120f8565b34801561064e57600080fd5b5061067b6004803603604081101561066557600080fd5b506001600160a01b0381351690602001356120fe565b604080516001600160a01b039098168852602088019690965286860194909452606086019290925215156080850152151560a084015260c0830152519081900360e00190f35b3480156106cd57600080fd5b50610223600480360360608110156106e457600080fd5b810190602081018135600160201b8111156106fe57600080fd5b82018360208201111561071057600080fd5b803590602001918460208302840111600160201b8311171561073157600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561078057600080fd5b82018360208201111561079257600080fd5b803590602001918460208302840111600160201b831117156107b357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561080257600080fd5b82018360208201111561081457600080fd5b803590602001918460208302840111600160201b8311171561083557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550612152945050505050565b34801561087f57600080fd5b5061025d6121ea565b34801561089457600080fd5b5061025d6121f0565b3480156108a957600080fd5b50610223600480360360808110156108c057600080fd5b50803590602081013590604081013590606001351515612200565b3480156108e757600080fd5b5061025d600480360360208110156108fe57600080fd5b50356001600160a01b0316612358565b34801561091a57600080fd5b506102236004803603602081101561093157600080fd5b5035612502565b34801561094457600080fd5b50610223600480360360a081101561095b57600080fd5b50803590602081013590604081013590606081013590608001351515612774565b34801561098857600080fd5b5061025d6004803603602081101561099f57600080fd5b50356001600160a01b03166128dc565b61022360048036036101608110156109c657600080fd5b810190602081018135600160201b8111156109e057600080fd5b8201836020820111156109f257600080fd5b803590602001918460208302840111600160201b83111715610a1357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505082359350506001600160a01b036020830135811692604081013592506060810135916080820135169060a081013515159060c08101359060e081013515159061010081013590610120013515156128ee565b610223600480360360e0811015610aaf57600080fd5b506001600160a01b03813581169160208101359160408201351690606081013590608081013515159060a08101359060c001351515612cf0565b348015610af557600080fd5b5061055b60048036036040811015610b0c57600080fd5b810190602081018135600160201b811115610b2657600080fd5b820183602082011115610b3857600080fd5b803590602001918460208302840111600160201b83111715610b5957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250612da1915050565b348015610ba557600080fd5b5061022360048036036080811015610bbc57600080fd5b50803590602081013590604081013590606001351515612fb8565b348015610be357600080fd5b5061022360048036036020811015610bfa57600080fd5b50356001600160a01b0316613181565b348015610c1657600080fd5b50610c4360048036036040811015610c2d57600080fd5b506001600160a01b03813516906020013561322b565b604080516001600160a01b039a8b168152988a1660208a015296909816878701526060870194909452608086019290925260a0850152151560c0840152151560e08301526101008201929092529051908190036101200190f35b348015610ca957600080fd5b5061022360048036036060811015610cc057600080fd5b506001600160a01b038135811691602081013591604090910135166133e8565b348015610cec57600080fd5b50610d1960048036036040811015610d0357600080fd5b506001600160a01b038135169060200135613851565b604080516001600160a01b039a8b1681526020810199909952968916888801529490971660608701526080860192909252151560a085015260c084015292151560e08301526101008201929092529051908190036101200190f35b348015610d8057600080fd5b5061025d60048036036020811015610d9757600080fd5b50356001600160a01b0316613937565b348015610db357600080fd5b50610223600480360360c0811015610dca57600080fd5b506001600160a01b0381358116916020810135821691604082013581169160608101359091169060808101359060a00135613949565b348015610e0c57600080fd5b50610e3960048036036040811015610e2357600080fd5b506001600160a01b038135169060200135613ab8565b604080516001600160a01b039a8b168152988a1660208a0152888101979097529490971660608701526080860192909252151560a085015260c084015292151560e08301526101008201929092529051908190036101200190f35b348015610ea057600080fd5b506103bf613b22565b348015610eb557600080fd5b5061022360048036036020811015610ecc57600080fd5b5035613b31565b348015610edf57600080fd5b506103bf613e6e565b348015610ef457600080fd5b506103bf613e7d565b348015610f0957600080fd5b5061022360048036036020811015610f2057600080fd5b5035613e8c565b60066020526000908152604090205481565b600080600080600080600080610f4d614fd0565b505050506001600160a01b03968716600090815260036020818152604080842099845298815291889020885161012081018a5281548b16815260018201548b1693810184905260028201549981018a9052918101549099166060820181905260048a01546080830181905260058b015460ff908116151560a0850181905260068d015460c0860181905260078e0154909216151560e086018190526008909d0154610100909501859052949c9a9b929a91995093975092955093509150565b60026000541415611052576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b600260005561105f61501c565b6001600160a01b038085166000908152600560209081526040808320878452825291829020825161010081018452815490941684526001810180548451818502810185019095528085529193858401939092908301828280156110eb57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116110cd575b5050509183525050600282015460208201526003820154604082015260048201546060820152600582015460ff8082161515608084015261010090910416151560a082015260069091015460c09091015280519091506001600160a01b0316611189576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b8060a00151156111e0576111a581602001518260800151612da1565b6111e05760405162461bcd60e51b81526004018080602001828103825260268152602001806152f16026913960400191505060405180910390fd5b6001600160a01b0384166000908152600560209081526040808320868452909152812080546001600160a01b03191681559061121f600183018261506e565b50600060028201819055600382018190556004820181905560058201805461ffff191690556006909101819055600b54604083015160208401518051611292946001600160a01b03909416939061127257fe5b60200260200101516001600160a01b0316613f1d9092919063ffffffff16565b600854602082015180516000926001600160a01b0316919060001981019081106112b857fe5b60200260200101516001600160a01b03161480156112d757508160c001515b15611304576112ef8260200151836060015130613f74565b90506112ff8183600001516140a3565b61131e565b61131b826020015183606001518460000151613f74565b90505b61132c8260e00151846140a3565b846001600160a01b03167f7e1fe496989eea92b738a562dbf9c0ae6aa6fcf3f1ef09e95ee4f7603721706b858460200151856040015186606001518688608001518960a001518a60c001518b60e00151604051808a8152602001806020018981526020018881526020018781526020018681526020018515158152602001841515815260200183815260200182810382528a818151815260200191508051906020019060200280838360005b838110156113f05781810151838201526020016113d8565b505050509050019a505050505050505050505060405180910390a250506001600055505050565b6007546001600160a01b0316331461146d576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600d8190556040805182815290517fe46d9daf6d25f7615efa1d0183b90ac6759d85014b598e409aadf0fd918d59a69181900360200190a150565b600260005414156114ee576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000556114fb614fd0565b506001600160a01b03808416600090815260036020818152604080842087855282529283902083516101208101855281548616808252600183015487169382019390935260028201549481019490945291820154909316606083015260048101546080830152600581015460ff908116151560a0840152600682015460c0840152600782015416151560e083015260080154610100820152906115d3576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b60006115f48260e001518360c0015184606001518560a00151156001611f8f565b506001600160a01b0380871660009081526003602081815260408084208a8552825280842080546001600160a01b0319908116825560018201805482169055600282018690559381018054909416909355600480840185905560058401805460ff199081169091556006850186905560078501805490911690556008909301849055600a5488518984015160608b01518b85015160808d015160a08e01518751632662166b60e01b8152958c1699860199909952928a16602485015290891660448401526064830152608482015293151560a48501523060c4850152905195965092949290931692632662166b9260e48084019382900301818787803b1580156116fd57600080fd5b505af1158015611711573d6000803e3d6000fd5b505050506040513d602081101561172757600080fd5b505160085460208501519192506001600160a01b039182169116141561175a576117558184600001516140a3565b611777565b82516020840151611777916001600160a01b039091169083613f1d565b611786836101000151856140a3565b82600001516001600160a01b03167f9a382661d6573da86db000471303be6f0b2b1bb66089b08e3c16a85d7b6e94f88685602001518660400151876060015188608001518960a001518a60c001518b60e001518c61010001518c604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001878152602001861515815260200185815260200184151581526020018381526020018281526020019a505050505050505050505060405180910390a25050600160005550505050565b6007546001600160a01b031681565b600260005414156118ac576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000819055885114806118c2575087516003145b611901576040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b8760018951038151811061191157fe5b60200260200101516001600160a01b03168860008151811061192f57fe5b60200260200101516001600160a01b0316141561198e576040805162461bcd60e51b815260206004820152601860248201527709ee4c8cae484deded67440d2dcecc2d8d2c840bee0c2e8d60431b604482015290519081900360640190fd5b600087116119e2576040805162461bcd60e51b815260206004820152601c60248201527b27b93232b92137b7b59d1034b73b30b634b2102fb0b6b7bab73a24b760211b604482015290519081900360640190fd5b600c54831015611a235760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b611a2b614120565b8115611ae65760085488516001600160a01b03909116908990600090611a4d57fe5b60200260200101516001600160a01b031614611a9a5760405162461bcd60e51b81526004018080602001828103825260258152602001806152606025913960400191505060405180910390fd5b611aa48388614192565b3414611ae15760405162461bcd60e51b81526004018080602001828103825260268152602001806152cb6026913960400191505060405180910390fd5b611bd0565b823414611b245760405162461bcd60e51b815260040180806020018281038252602e815260200180615361602e913960400191505060405180910390fd5b600a5488516001600160a01b0390911690631b827878908a90600090611b4657fe5b602002602001015133308b6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b158015611bb757600080fd5b505af1158015611bcb573d6000803e3d6000fd5b505050505b611be0338989898989878a6141ea565b50506001600055505050505050565b6001602081815260009384526040808520909152918352912080549181015460028201546003830154600484015460058501546006860154600787015460088801546009909801546001600160a01b03998a1699978816989697958616969490951694929360ff92831693919216908a565b600e5460ff1681565b6008546001600160a01b031681565b60026000541415611cbf576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055611ccc61508f565b5033600090815260016020818152604080842085855282529283902083516101408101855281546001600160a01b039081168083529483015481169382019390935260028201549481019490945260038101548216606085015260048101549091166080840152600581015460a0840152600681015460ff908116151560c0850152600782015460e0850152600882015416151561010084015260090154610120830152611daf576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b3360009081526001602081815260408084208685528252832080546001600160a01b0319908116825592810180548416905560028101849055600381018054841690556004810180549093169092556005820183905560068201805460ff19908116909155600783018490556008808401805490921690915560099092019290925554908201516001600160a01b0390811691161415611e7357611e6e611e68826040015183610120015161419290919063ffffffff16565b336140a3565b611ea8565b611e9933826040015183602001516001600160a01b0316613f1d9092919063ffffffff16565b611ea8816101200151336140a3565b80600001516001600160a01b03167fd500f34e0ec655b7614ae42e1d9c666d5e4dde909a1297829f8c5ecf00805d328383602001518460400151856060015186608001518760a001518860c001518960e001518a61010001518b6101200151604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001876001600160a01b03168152602001868152602001851515815260200184815260200183151581526020018281526020019a505050505050505050505060405180910390a250506001600055565b670de0b6b3a764000081565b60008060008461201757600b54604080516340d3096b60e11b81526001600160a01b038981166004830152915191909216916381a612d6916024808301926020929190829003018186803b158015611fe657600080fd5b505afa158015611ffa573d6000803e3d6000fd5b505050506040513d602081101561201057600080fd5b5051612091565b600b5460408051637092736960e11b81526001600160a01b0389811660048301529151919092169163e124e6d2916024808301926020929190829003018186803b15801561206457600080fd5b505afa158015612078573d6000803e3d6000fd5b505050506040513d602081101561208e57600080fd5b50515b90506000886120a2578782106120a6565b8782115b905084156120ea57806120ea5760405162461bcd60e51b81526004018080602001828103825260268152602001806152f16026913960400191505060405180910390fd5b909890975095505050505050565b600c5481565b600560208181526000938452604080852090915291835291208054600282015460038301546004840154948401546006909401546001600160a01b03909316949193909260ff808316926101009004169087565b60005b83518110156121825761217a84828151811061216d57fe5b6020026020010151613b31565b600101612155565b5060005b82518110156121b3576121ab83828151811061219e57fe5b6020026020010151611c79565b600101612186565b5060005b81518110156121e4576121dc8282815181106121cf57fe5b6020026020010151612502565b6001016121b7565b50505050565b600d5481565b68327cb2734119d3b7a9601e1b81565b60026000541415612246576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600160209081526040808320878452909152902080546001600160a01b03166122ae576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b6007810183905560088101805483151560ff19909116811790915560058201859055600382015460048301546006840154604080518a81526001600160a01b039485166020820152929093168284015260ff16151560608201526080810187905260a0810186905260c08101929092525133917f0a0360dd5c354235bbf8d386ba3b24ef8134088e0785677de1504df219d9149a919081900360e00190a250506001600055505050565b600b5460408051632c668ec160e01b81526001600160a01b038481166004830152670de0b6b3a76400006024830152915160009384931691632c668ec1916044808301926020929190829003018186803b1580156123b557600080fd5b505afa1580156123c9573d6000803e3d6000fd5b505050506040513d60208110156123df57600080fd5b5051600b54604080516340d3096b60e11b81526001600160a01b038781166004830152915193945060009391909216916381a612d6916024808301926020929190829003018186803b15801561243457600080fd5b505afa158015612448573d6000803e3d6000fd5b505050506040513d602081101561245e57600080fd5b5051600b54604080516323b95ceb60e21b81526001600160a01b03888116600483015291519394506000939190921691638ee573ac916024808301926020929190829003018186803b1580156124b357600080fd5b505afa1580156124c7573d6000803e3d6000fd5b505050506040513d60208110156124dd57600080fd5b505190506124f9600a82900a6124f385856143f9565b90614452565b95945050505050565b60026000541415612548576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612555614fd0565b5033600090815260036020818152604080842085855282529283902083516101208101855281546001600160a01b03908116808352600184015482169483019490945260028301549582019590955292810154909316606083015260048301546080830152600583015460ff908116151560a0840152600684015460c0840152600784015416151560e08301526008909201546101008201529061262e576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b336000818152600360208181526040808420878552909152822080546001600160a01b03199081168255600182018054821690556002820184905591810180549092169091556004810182905560058101805460ff19908116909155600682018390556007820180549091169055600801556101008201516126af916140a3565b80600001516001600160a01b03167f1154174c82984656b028c8021671988f60a346497e56fe02554761184f82a0758383602001518460400151856060015186608001518760a001518860c001518960e001518a6101000151604051808a8152602001896001600160a01b03168152602001888152602001876001600160a01b0316815260200186815260200185151581526020018481526020018315158152602001828152602001995050505050505050505060405180910390a250506001600055565b600260005414156127ba576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600360209081526040808320888452909152902080546001600160a01b0316612822576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b6006810183905560078101805483151560ff1990911681179091556004820185905560028201869055600182015460038301546005840154604080518b81526001600160a01b0394851660208201528082018b90529290931660608301526080820188905260ff16151560a082015260c0810186905260e08101929092525133917f75781255bc71c83f89f29e5a2599f2c174a562d2cd8f2e818a47f132e728049891908190036101000190a25050600160005550505050565b60026020526000908152604090205481565b60026000541415612934576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612941614120565b600c548210156129825760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b8015612a3d576008548b516001600160a01b03909116908c906000906129a457fe5b60200260200101516001600160a01b0316146129f15760405162461bcd60e51b81526004018080602001828103825260258152602001806152606025913960400191505060405180910390fd5b6129fb828b614192565b3414612a385760405162461bcd60e51b81526004018080602001828103825260268152602001806152cb6026913960400191505060405180910390fd5b612b27565b813414612a7b5760405162461bcd60e51b815260040180806020018281038252602e815260200180615361602e913960400191505060405180910390fd5b600a548b516001600160a01b0390911690631b827878908d90600090612a9d57fe5b602002602001015133308e6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b158015612b0e57600080fd5b505af1158015612b22573d6000803e3d6000fd5b505050505b60008b60018d510381518110612b3957fe5b60200260200101519050600060018d511115612bfe57816001600160a01b03168d600081518110612b6657fe5b60200260200101516001600160a01b03161415612bc5576040805162461bcd60e51b815260206004820152601860248201527709ee4c8cae484deded67440d2dcecc2d8d2c840bee0c2e8d60431b604482015290519081900360640190fd5b612bec600b60009054906101000a90046001600160a01b03168d8f60008151811061127257fe5b612bf78d8b30613f74565b9050612c01565b508a5b600b5460408051630a48d5a960e01b81526001600160a01b0385811660048301526024820185905291516000939290921691630a48d5a991604480820192602092909190829003018186803b158015612c5957600080fd5b505afa158015612c6d573d6000803e3d6000fd5b505050506040513d6020811015612c8357600080fd5b5051600d54909150811015612cc95760405162461bcd60e51b81526004018080602001828103825260228152602001806151bd6022913960400191505060405180910390fd5b50612cdc3383838b8f8e8d8d8d8d614491565b505060016000555050505050505050505050565b60026000541415612d36576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612d43614120565b600c543411612d835760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b612d933386868a8a888888614764565b505060016000555050505050565b6000825160021480612db4575082516003145b612df3576040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b600083600081518110612e0257fe5b60200260200101519050600084600186510381518110612e1e57fe5b602090810291909101015160095490915060009081906001600160a01b0385811691161415612e6b57612e6487600181518110612e5757fe5b6020026020010151612358565b9150612ee7565b600b54604080516340d3096b60e11b81526001600160a01b038781166004830152915191909216916381a612d6916024808301926020929190829003018186803b158015612eb857600080fd5b505afa158015612ecc573d6000803e3d6000fd5b505050506040513d6020811015612ee257600080fd5b505191505b6009546001600160a01b0384811691161415612f10575068327cb2734119d3b7a9601e1b612f8c565b600b5460408051637092736960e11b81526001600160a01b0386811660048301529151919092169163e124e6d2916024808301926020929190829003018186803b158015612f5d57600080fd5b505afa158015612f71573d6000803e3d6000fd5b505050506040513d6020811015612f8757600080fd5b505190505b6000612fa8836124f38468327cb2734119d3b7a9601e1b6143f9565b8710955050505050505b92915050565b60026000541415612ffe576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600560209081526040808320878452909152902080546001600160a01b0316613066576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b838160030181905550828160040181905550818160050160006101000a81548160ff021916908315150217905550336001600160a01b03167fa7f9f4a25eb76f5ec01b1a429d95d6a00833f0f137c88827c58799a1c1ff0dfe868360010184600201548888888860050160019054906101000a900460ff168960060154604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818154815260200191508054801561316057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613142575b5050995050505050505050505060405180910390a250506001600055505050565b6007546001600160a01b031633146131d7576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600780546001600160a01b0383166001600160a01b0319909116811790915560408051918252517fe24c39186e9137521953beaa8446e71f55b8f12296984f9d4273ceb1af728d909181900360200190a150565b600080600080600080600080600061324161501c565b6001600160a01b03808d1660009081526005602090815260408083208f8452825291829020825161010081018452815490941684526001810180548451818502810185019095528085529193858401939092908301828280156132cd57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116132af575b505050918352505060028201546020808301919091526003830154604083015260048301546060830152600583015460ff8082161515608085015261010090910416151560a083015260069092015460c0909101528101515190915061333457600061334e565b806020015160008151811061334557fe5b60200260200101515b60018260200151511161336257600061337c565b816020015160018151811061337357fe5b60200260200101515b6002836020015151116133905760006133aa565b82602001516002815181106133a157fe5b60200260200101515b8360400151846060015185608001518660a001518760c001518860e00151995099509950995099509950995099509950509295985092959850929598565b6002600054141561342e576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b600260005561343b61508f565b506001600160a01b038084166000908152600160208181526040808420878552825292839020835161014081018552815486168082529382015486169281019290925260028101549382019390935260038301548416606082015260048301549093166080840152600582015460a0840152600682015460ff908116151560c0850152600783015460e08501526008830154161515610100840152600990910154610120830152613521576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b60006135428261010001518360e0015184608001518560c001516001611f8f565b506001600160a01b0380871660009081526001602081815260408084208a8552825280842080546001600160a01b0319908116825593810180548516905560028101859055600381018054851690556004810180549094169093556005830184905560068301805460ff19908116909155600784018590556008840180549091169055600990920192909255600b5490860151918601519394506135ea938316921690613f1d565b81606001516001600160a01b031682602001516001600160a01b0316146136c257604080516002808252606080830184529260208301908036833701905050905082602001518160008151811061363d57fe5b60200260200101906001600160a01b031690816001600160a01b03168152505082606001518160018151811061366f57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050600061369d82600030613f74565b600b5460608601519192506136bf916001600160a01b03908116911683613f1d565b50505b600a5482516060840151608085015160a086015160c087015160408051630f8ee8bb60e11b81526001600160a01b03968716600482015294861660248601529285166044850152606484019190915215156084830152519190921691631f1dd1769160a480830192600092919082900301818387803b15801561374457600080fd5b505af1158015613758573d6000803e3d6000fd5b5050505061376b826101200151846140a3565b81600001516001600160a01b03167f7fb1c74d1ea6aa1c9c585e17ce8274c8ff98745e85e7459b73f87d784494f58e8584602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001518c604051808c81526020018b6001600160a01b031681526020018a8152602001896001600160a01b03168152602001886001600160a01b03168152602001878152602001861515815260200185815260200184151581526020018381526020018281526020019b50505050505050505050505060405180910390a250506001600055505050565b600080600080600080600080600061386761508f565b505050506001600160a01b0397881660009081526001602081815260408084209a845299815291899020895161014081018b5281548c168152918101548b1692820183905260028101549982018a905260038101548b16606083018190526004820154909b1660808301819052600582015460a08401819052600683015460ff908116151560c08601819052600785015460e08701819052600886015490921615156101008701819052600990950154610120909601869052959e9c9d9c929b5090995093975092955093509150565b60046020526000908152604090205481565b6007546001600160a01b0316331461399f576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600e5460ff16156139f7576040805162461bcd60e51b815260206004820152601e60248201527f4f72646572426f6f6b3a20616c726561647920696e697469616c697a65640000604482015290519081900360640190fd5b600e805460ff19166001179055600a80546001600160a01b038089166001600160a01b03199283168117909355600b8054898316908416811790915560088054898416908516811790915560098054938916939094168317909355600c869055600d8590556040805194855260208501919091528381019290925260608301526080820184905260a08201839052517fcfb7ef8749fafc8da2af1ba3d025479ffc4e58f7dc420113e112512a3bda59639181900360c00190a1505050505050565b600360208181526000938452604080852090915291835291208054600182015460028301549383015460048401546005850154600686015460078701546008909701546001600160a01b039687169895871697959690941694929360ff9283169391929091169089565b6009546001600160a01b031681565b60026000541415613b77576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055613b8461501c565b33600090815260056020908152604080832085845282529182902082516101008101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015613c0d57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613bef575b5050509183525050600282015460208201526003820154604082015260048201546060820152600582015460ff8082161515608084015261010090910416151560a082015260069091015460c09091015280519091506001600160a01b0316613cab576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b336000908152600560209081526040808320858452909152812080546001600160a01b031916815590613ce1600183018261506e565b50600060028201819055600382018190556004820181905560058201805461ffff191690556006909101819055600854602083015180516001600160a01b03909216929091613d2c57fe5b60200260200101516001600160a01b03161415613d6657613d61611e6882604001518360e0015161419290919063ffffffff16565b613d8e565b613d80338260400151836020015160008151811061127257fe5b613d8e8160e00151336140a3565b336001600160a01b03167fefd66d4f9c2f880c70aedeb5b26a44fb474cea07e5d6c533f2d27c303d5d94538383602001518460400151856060015186608001518760a001518860c001518960e00151604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818151815260200191508051906020019060200280838360005b83811015613e4b578181015183820152602001613e33565b50505050905001995050505050505050505060405180910390a250506001600055565b600a546001600160a01b031681565b600b546001600160a01b031681565b6007546001600160a01b03163314613ee2576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600c8190556040805182815290517fbde5eafdc37b81830d70124cddccaaa6d034e71dda3c8fc18a959ca76a7cbcfc9181900360200190a150565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052613f6f9084906149da565b505050565b6000835160021415613fbb57613fb484600081518110613f9057fe5b602002602001015185600181518110613fa557fe5b60200260200101518585614a8b565b905061409c565b835160031415614061576000613ffc85600081518110613fd757fe5b602002602001015186600181518110613fec57fe5b6020026020010151600030614a8b565b9050614025600b60009054906101000a90046001600160a01b0316828760018151811061127257fe5b6140598560018151811061403557fe5b60200260200101518660028151811061404a57fe5b60200260200101518686614a8b565b91505061409c565b6040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b9392505050565b60085460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156140f057600080fd5b505af1158015614104573d6000803e3d6000fd5b5061411c925050506001600160a01b03821683614c6a565b5050565b341561419057600860009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561417657600080fd5b505af115801561418a573d6000803e3d6000fd5b50505050505b565b60008282018381101561409c576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6001600160a01b03881660009081526006602052604090205461420b61501c565b6040518061010001604052808b6001600160a01b031681526020018a81526020018981526020018881526020018781526020018615158152602001851515815260200184815250905061426860018361419290919063ffffffff16565b6001600160a01b038b8116600090815260066020908152604080832094909455600581528382208683528152929020835181546001600160a01b031916921691909117815582820151805184936142c69260018501929101906150e3565b5060408201518160020155606082015181600301556080820151816004015560a08201518160050160006101000a81548160ff02191690831515021790555060c08201518160050160016101000a81548160ff02191690831515021790555060e08201518160060155905050896001600160a01b03167fdf06bb56ffc4029dc0b62b68bb5bbadea93a38b530cefc9b81afb742a6555d88838b8b8b8b8b8b8b604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818151815260200191508051906020019060200280838360005b838110156143d35781810151838201526020016143bb565b50505050905001995050505050505050505060405180910390a250505050505050505050565b60008261440857506000612fb2565b8282028284828161441557fe5b041461409c5760405162461bcd60e51b81526004018080602001828103825260218152602001806152aa6021913960400191505060405180910390fd5b600061409c83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250614d4f565b336000908152600260205260409020546144a961508f565b6040518061014001604052808d6001600160a01b031681526020018c6001600160a01b031681526020018b81526020018a6001600160a01b03168152602001896001600160a01b031681526020018881526020018715158152602001868152602001851515815260200184815250905061452d60018361419290919063ffffffff16565b600260008e6001600160a01b03166001600160a01b031681526020019081526020016000208190555080600160008e6001600160a01b03166001600160a01b03168152602001908152602001600020600084815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816002015560608201518160030160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060808201518160040160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060a0820151816005015560c08201518160060160006101000a81548160ff02191690831515021790555060e082015181600701556101008201518160080160006101000a81548160ff02191690831515021790555061012082015181600901559050508b6001600160a01b03167fb27b9afe3043b93788c40cfc3cc73f5d928a2e40f3ba01820b246426de8fa1b9838d8d8d8d8d8d8d8d8d604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001876001600160a01b03168152602001868152602001851515815260200184815260200183151581526020018281526020019a505050505050505050505060405180910390a2505050505050505050505050565b6001600160a01b038816600090815260046020526040902054614785614fd0565b5060408051610120810182526001600160a01b03808c1682528a8116602083015291810189905290871660608201526080810186905284151560a082015260c0810184905282151560e0820152346101008201526147e4826001614192565b600460008c6001600160a01b03166001600160a01b031681526020019081526020016000208190555080600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020600084815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816002015560608201518160030160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816004015560a08201518160050160006101000a81548160ff02191690831515021790555060c0820151816006015560e08201518160070160006101000a81548160ff0219169083151502179055506101008201518160080155905050896001600160a01b03167f48ee333d2a65cc45fdb83bc012920d89181c3377390cd239d2b63f2bef67a02d838b8b8b8b8b8b8b34604051808a8152602001896001600160a01b03168152602001888152602001876001600160a01b0316815260200186815260200185151581526020018481526020018315158152602001828152602001995050505050505050505060405180910390a250505050505050505050565b6060614a2f826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614df19092919063ffffffff16565b805190915015613f6f57808060200190516020811015614a4e57600080fd5b5051613f6f5760405162461bcd60e51b815260040180806020018281038252602a815260200180615337602a913960400191505060405180910390fd5b60095460009081906001600160a01b0386811691161415614b3057600b546040805163817bb85760e01b81526001600160a01b03898116600483015286811660248301529151919092169163817bb8579160448083019260209291908290030181600087803b158015614afd57600080fd5b505af1158015614b11573d6000803e3d6000fd5b505050506040513d6020811015614b2757600080fd5b50519050614c2b565b6009546001600160a01b0387811691161415614b9d57600b5460408051630711e61960e41b81526001600160a01b03888116600483015286811660248301529151919092169163711e61909160448083019260209291908290030181600087803b158015614afd57600080fd5b600b5460408051634998b10960e11b81526001600160a01b038981166004830152888116602483015286811660448301529151919092169163933162129160648083019260209291908290030181600087803b158015614bfc57600080fd5b505af1158015614c10573d6000803e3d6000fd5b505050506040513d6020811015614c2657600080fd5b505190505b838110156124f95760405162461bcd60e51b815260040180806020018281038252602181526020018061523f6021913960400191505060405180910390fd5b80471015614cbf576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114614d0a576040519150601f19603f3d011682016040523d82523d6000602084013e614d0f565b606091505b5050905080613f6f5760405162461bcd60e51b815260040180806020018281038252603a8152602001806151df603a913960400191505060405180910390fd5b60008183614ddb5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015614da0578181015183820152602001614d88565b50505050905090810190601f168015614dcd5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506000838581614de757fe5b0495945050505050565b6060614e008484600085614e08565b949350505050565b606082471015614e495760405162461bcd60e51b81526004018080602001828103825260268152602001806152196026913960400191505060405180910390fd5b614e5285614f64565b614ea3576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b60208310614ee25780518252601f199092019160209182019101614ec3565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114614f44576040519150601f19603f3d011682016040523d82523d6000602084013e614f49565b606091505b5091509150614f59828286614f6a565b979650505050505050565b3b151590565b60608315614f7957508161409c565b825115614f895782518084602001fd5b60405162461bcd60e51b8152602060048201818152845160248401528451859391928392604401919085019080838360008315614da0578181015183820152602001614d88565b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915290565b60405180610100016040528060006001600160a01b0316815260200160608152602001600081526020016000815260200160008152602001600015158152602001600015158152602001600081525090565b508054600082559060005260206000209081019061508c9190615148565b50565b6040805161014081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081019190915290565b828054828255906000526020600020908101928215615138579160200282015b8281111561513857825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190615103565b5061514492915061515d565b5090565b5b808211156151445760008155600101615149565b5b808211156151445780546001600160a01b031916815560010161515e56fe4f72646572426f6f6b3a206e6f6e2d6578697374656e74206f726465720000005265656e7472616e637947756172643a207265656e7472616e742063616c6c004f72646572426f6f6b3a20696e73756666696369656e7420636f6c6c61746572616c416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c4f72646572426f6f6b3a20696e73756666696369656e7420616d6f756e744f75744f72646572426f6f6b3a206f6e6c79207765746820636f756c6420626520777261707065644f72646572426f6f6b3a20696e73756666696369656e7420657865637574696f6e20666565536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774f72646572426f6f6b3a20696e636f72726563742076616c7565207472616e736665727265644f72646572426f6f6b3a20696e76616c696420707269636520666f7220657865637574696f6e4f72646572426f6f6b3a20696e76616c6964205f706174682e6c656e677468005361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565644f72646572426f6f6b3a20696e636f727265637420657865637574696f6e20666565207472616e73666572726564a2646970667358221220e85a1f264782d8faab5f0f3dab3cf5b8844d298c594ad34ab45699fcaa9855e464736f6c634300060c0033 \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.abi b/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.abi new file mode 100644 index 0000000..e2ffcc2 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address payable","name":"_orderBookAddress","type":"address"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256[]","name":"_indices","type":"uint256[]"}],"name":"getDecreaseOrders","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_orderBookAddress","type":"address"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256[]","name":"_indices","type":"uint256[]"}],"name":"getIncreaseOrders","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_orderBookAddress","type":"address"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256[]","name":"_indices","type":"uint256[]"}],"name":"getSwapOrders","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.bin b/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.bin new file mode 100644 index 0000000..7392c14 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/OrderBookReader.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610e27806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80630ce933b9146100465780632e18146914610199578063c38ccd5014610253575b600080fd5b6101006004803603606081101561005c57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561008f57600080fd5b8201836020820111156100a157600080fd5b803590602001918460208302840111600160201b831117156100c257600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061030d945050505050565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561014457818101518382015260200161012c565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561018357818101518382015260200161016b565b5050505090500194505050505060405180910390f35b610100600480360360608110156101af57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b8111156101e257600080fd5b8201836020820111156101f457600080fd5b803590602001918460208302840111600160201b8311171561021557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610675945050505050565b6101006004803603606081101561026957600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561029c57600080fd5b8201836020820111156102ae57600080fd5b803590602001918460208302840111600160201b831117156102cf57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610a17945050505050565b606080610318610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160028152509050606084518260600151026001600160401b038111801561036d57600080fd5b50604051908082528060200260200182016040528015610397578160200160208202803683370190505b509050606085518360800151026001600160401b03811180156103b957600080fd5b506040519080825280602002602001820160405280156103e3578160200160208202803683370190505b509050875b865184511015610667578684600001518151811061040257fe5b60200260200101518460200181815250506000806000806000806000876001600160a01b031663026032ee8c604001518d602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101006040518083038186803b15801561047c57600080fd5b505afa158015610490573d6000803e3d6000fd5b505050506040513d6101008110156104a757600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190505050509650965096509650965096509650858a8c606001518d60000151028151811061052457fe5b602002602001018181525050838a8c606001518d60000151026001018151811061054a57fe5b60200260200101818152505082610562576000610565565b60015b60ff168a8c606001518d60000151026002018151811061058157fe5b602002602001018181525050818a8c606001518d6000015102600301815181106105a757fe5b602002602001018181525050806105bf5760006105c2565b60015b60ff168a8c606001518d6000015102600401815181106105de57fe5b60200260200101818152505086898c608001518d60000151028151811061060157fe5b60200260200101906001600160a01b031690816001600160a01b03168152505084898c608001518d60000151026001018151811061063b57fe5b6001600160a01b0390921660209283029190910190910152505088516001018952506103e89350505050565b509097909650945050505050565b606080610680610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160038152509050606084518260600151026001600160401b03811180156106d557600080fd5b506040519080825280602002602001820160405280156106ff578160200160208202803683370190505b509050606085518360800151026001600160401b038111801561072157600080fd5b5060405190808252806020026020018201604052801561074b578160200160208202803683370190505b509050875b865184511015610667578684600001518151811061076a57fe5b6020026020010151846020018181525050600080600080600080600080886001600160a01b031663d0d40cd68d604001518e602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101206040518083038186803b1580156107e557600080fd5b505afa1580156107f9573d6000803e3d6000fd5b505050506040513d61012081101561081057600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291905050505097509750975097509750975097509750848b8d606001518e60000151028151811061089957fe5b602002602001018181525050838b8d606001518e6000015102600101815181106108bf57fe5b602002602001018181525050828b8d606001518e6000015102600201815181106108e557fe5b602002602001018181525050816108fd576000610900565b60015b60ff168b8d606001518e60000151026003018151811061091c57fe5b60200260200101818152505080610934576000610937565b60015b60ff168b8d606001518e60000151026004018151811061095357fe5b602002602001018181525050878a8d608001518e60000151028151811061097657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050868a8d608001518e6000015102600101815181106109b057fe5b60200260200101906001600160a01b031690816001600160a01b031681525050858a8d608001518e6000015102600201815181106109ea57fe5b6001600160a01b0390921660209283029190910190910152505089516001018a5250610750945050505050565b606080610a22610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160038152509050606084518260600151026001600160401b0381118015610a7757600080fd5b50604051908082528060200260200182016040528015610aa1578160200160208202803683370190505b509050606085518360800151026001600160401b0381118015610ac357600080fd5b50604051908082528060200260200182016040528015610aed578160200160208202803683370190505b509050875b8651845110156106675786846000015181518110610b0c57fe5b6020026020010151846020018181525050600080600080600080600080886001600160a01b031663d3bab1d18d604001518e602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101206040518083038186803b158015610b8757600080fd5b505afa158015610b9b573d6000803e3d6000fd5b505050506040513d610120811015610bb257600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291905050505097509750975097509750975097509750868b8d606001518e600001510281518110610c3b57fe5b602002602001018181525050838b8d606001518e600001510260010181518110610c6157fe5b60200260200101818152505082610c79576000610c7c565b60015b60ff168b8d606001518e600001510260020181518110610c9857fe5b602002602001018181525050818b8d606001518e600001510260030181518110610cbe57fe5b60200260200101818152505080610cd6576000610cd9565b60015b60ff168b8d606001518e600001510260040181518110610cf557fe5b602002602001018181525050878a8d608001518e600001510281518110610d1857fe5b60200260200101906001600160a01b031690816001600160a01b031681525050858a8d608001518e600001510260010181518110610d5257fe5b60200260200101906001600160a01b031690816001600160a01b031681525050848a8d608001518e600001510260020181518110610d8c57fe5b6001600160a01b0390921660209283029190910190910152505089516001018a5250610af2945050505050565b6040518060a00160405280600081526020016000815260200160006001600160a01b031681526020016000815260200160008152509056fea26469706673582212200490431a3ff46032daa47d7d2efe24198ccd39e8e57ff9d6fccef3b97ebe51b664736f6c634300060c0033 \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/PositionRouter.abi b/src/Managing.Tools.ABI/Gmx/core/PositionRouter.abi new file mode 100644 index 0000000..2c78a5b --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/PositionRouter.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_router","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_shortsTracker","type":"address"},{"internalType":"uint256","name":"_depositFee","type":"uint256"},{"internalType":"uint256","name":"_minExecutionFee","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"callbackTarget","type":"address"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"Callback","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockGap","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeGap","type":"uint256"}],"name":"CancelDecreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockGap","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeGap","type":"uint256"}],"name":"CancelIncreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"queueIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockTime","type":"uint256"}],"name":"CreateDecreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"queueIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasPrice","type":"uint256"}],"name":"CreateIncreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"marginFeeBasisPoints","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"referralCode","type":"bytes32"},{"indexed":false,"internalType":"address","name":"referrer","type":"address"}],"name":"DecreasePositionReferral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockGap","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeGap","type":"uint256"}],"name":"ExecuteDecreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address[]","name":"path","type":"address[]"},{"indexed":false,"internalType":"address","name":"indexToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"blockGap","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeGap","type":"uint256"}],"name":"ExecuteIncreasePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"marginFeeBasisPoints","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"referralCode","type":"bytes32"},{"indexed":false,"internalType":"address","name":"referrer","type":"address"}],"name":"IncreasePositionReferral","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"admin","type":"address"}],"name":"SetAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"callbackGasLimit","type":"uint256"}],"name":"SetCallbackGasLimit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minBlockDelayKeeper","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minTimeDelayPublic","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxTimeDelay","type":"uint256"}],"name":"SetDelayValues","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"depositFee","type":"uint256"}],"name":"SetDepositFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"increasePositionBufferBps","type":"uint256"}],"name":"SetIncreasePositionBufferBps","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isLeverageEnabled","type":"bool"}],"name":"SetIsLeverageEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"longSizes","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"shortSizes","type":"uint256[]"}],"name":"SetMaxGlobalSizes","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minExecutionFee","type":"uint256"}],"name":"SetMinExecutionFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bool","name":"isActive","type":"bool"}],"name":"SetPositionKeeper","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"referralStorage","type":"address"}],"name":"SetReferralStorage","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"increasePositionRequestKeysStart","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"decreasePositionRequestKeysStart","type":"uint256"}],"name":"SetRequestKeysStartValues","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawFees","type":"event"},{"inputs":[],"name":"BASIS_POINTS_DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"callbackGasLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"cancelDecreasePosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"cancelIncreasePosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_acceptablePrice","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_executionFee","type":"uint256"},{"internalType":"bool","name":"_withdrawETH","type":"bool"},{"internalType":"address","name":"_callbackTarget","type":"address"}],"name":"createDecreasePosition","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_acceptablePrice","type":"uint256"},{"internalType":"uint256","name":"_executionFee","type":"uint256"},{"internalType":"bytes32","name":"_referralCode","type":"bytes32"},{"internalType":"address","name":"_callbackTarget","type":"address"}],"name":"createIncreasePosition","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_acceptablePrice","type":"uint256"},{"internalType":"uint256","name":"_executionFee","type":"uint256"},{"internalType":"bytes32","name":"_referralCode","type":"bytes32"},{"internalType":"address","name":"_callbackTarget","type":"address"}],"name":"createIncreasePositionETH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"decreasePositionRequestKeys","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decreasePositionRequestKeysStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"decreasePositionRequests","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"collateralDelta","type":"uint256"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"executionFee","type":"uint256"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"blockTime","type":"uint256"},{"internalType":"bool","name":"withdrawETH","type":"bool"},{"internalType":"address","name":"callbackTarget","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"decreasePositionsIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"executeDecreasePosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_endIndex","type":"uint256"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"executeDecreasePositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"executeIncreasePosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_endIndex","type":"uint256"},{"internalType":"address payable","name":"_executionFeeReceiver","type":"address"}],"name":"executeIncreasePositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"feeReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"}],"name":"getDecreasePositionRequestPath","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"}],"name":"getIncreasePositionRequestPath","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getRequestKey","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getRequestQueueLengths","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gov","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"increasePositionBufferBps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"increasePositionRequestKeys","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"increasePositionRequestKeysStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"increasePositionRequests","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"indexToken","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"sizeDelta","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint256","name":"acceptablePrice","type":"uint256"},{"internalType":"uint256","name":"executionFee","type":"uint256"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"blockTime","type":"uint256"},{"internalType":"bool","name":"hasCollateralInETH","type":"bool"},{"internalType":"address","name":"callbackTarget","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"increasePositionsIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isLeverageEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isPositionKeeper","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxGlobalLongSizes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxGlobalShortSizes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTimeDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBlockDelayKeeper","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minExecutionFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minTimeDelayPublic","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"referralStorage","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"sendValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_callbackGasLimit","type":"uint256"}],"name":"setCallbackGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minBlockDelayKeeper","type":"uint256"},{"internalType":"uint256","name":"_minTimeDelayPublic","type":"uint256"},{"internalType":"uint256","name":"_maxTimeDelay","type":"uint256"}],"name":"setDelayValues","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositFee","type":"uint256"}],"name":"setDepositFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gov","type":"address"}],"name":"setGov","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_increasePositionBufferBps","type":"uint256"}],"name":"setIncreasePositionBufferBps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isLeverageEnabled","type":"bool"}],"name":"setIsLeverageEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokens","type":"address[]"},{"internalType":"uint256[]","name":"_longSizes","type":"uint256[]"},{"internalType":"uint256[]","name":"_shortSizes","type":"uint256[]"}],"name":"setMaxGlobalSizes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minExecutionFee","type":"uint256"}],"name":"setMinExecutionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"bool","name":"_isActive","type":"bool"}],"name":"setPositionKeeper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_referralStorage","type":"address"}],"name":"setReferralStorage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_increasePositionRequestKeysStart","type":"uint256"},{"internalType":"uint256","name":"_decreasePositionRequestKeysStart","type":"uint256"}],"name":"setRequestKeysStartValues","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shortsTracker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/PositionRouter.bin b/src/Managing.Tools.ABI/Gmx/core/PositionRouter.bin new file mode 100644 index 0000000..2c8756d --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/PositionRouter.bin @@ -0,0 +1 @@ +608060405260646008556011805460ff191660011790553480156200002357600080fd5b50604051620060bb380380620060bb833981810160405260c08110156200004957600080fd5b508051602082015160408301516060840151608085015160a090950151600160008181558154336001600160a01b031991821681179093556003805482166001600160a01b03998a16179055600580548216978916979097179096556006805487169588169590951790945560079690965560048054851695909216949094179055600280549092169093179055600d55615fd0908190620000eb90396000f3fe60806040526004361061027e5760003560e01c80626cc35e146102d357806304225954146103045780630d4d003d146103405780631045c74e1461038d578063126082cf146103c057806312d43a51146103d55780631bca8cf0146103ea5780631ce9cb8f146103ff5780631f28510614610432578063225fc9fd146104d4578063233bfe3b1461050d57806324a084df1461053757806324f746971461057057806327b42c0f14610585578063308aa81f146105be5780633422ead1146105ee57806336eba48a146106295780633a2a80c71461065c5780633e72a262146106715780633fc8cef3146106865780634067b1321461069b5780634278555f146106d1578063490ae210146106fb5780635841fcaa146107255780635b88e8c61461073a5780635d5c22e81461081357806360a362e21461088d57806362f8a3fe146108c6578063633451de146108ff57806363ae210314610932578063657bc5d01461094757806367a527931461095c578063704b6c02146109715780637be7d141146109a45780637c2eb9f714610a8f5780638a54942f14610abb57806395e9bbd714610ae55780639698d25a14610b0f57806398d1e03a14610b425780639a20810014610b575780639b57862014610b90578063ae4d7f9a14610ba5578063cb0269c914610bd8578063cfad57a214610bed578063e1f21c6714610c20578063ef12c67e14610c63578063f255527814610e15578063f2ae372f14610e50578063f2cea6a514610f2f578063f3883d8b14610f6a578063f851a44014610fa3578063f887ea4014610fb8578063fa44457714610fcd578063faf990f314611000578063fbfa77cf14611099578063fc2cee62146110ae576102ce565b366102ce576006546001600160a01b031633146102cc5760405162461bcd60e51b8152600401808060200182810382526023815260200180615f4e6023913960400191505060405180910390fd5b005b600080fd5b3480156102df57600080fd5b506102e86110d8565b604080516001600160a01b039092168252519081900360200190f35b34801561031057600080fd5b5061032e6004803603602081101561032757600080fd5b50356110e7565b60408051918252519081900360200190f35b34801561034c57600080fd5b506103796004803603604081101561036357600080fd5b50803590602001356001600160a01b0316611105565b604080519115158252519081900360200190f35b34801561039957600080fd5b5061032e600480360360208110156103b057600080fd5b50356001600160a01b03166115a3565b3480156103cc57600080fd5b5061032e6115b5565b3480156103e157600080fd5b506102e86115bb565b3480156103f657600080fd5b5061032e6115ca565b34801561040b57600080fd5b5061032e6004803603602081101561042257600080fd5b50356001600160a01b03166115d0565b34801561043e57600080fd5b5061045c6004803603602081101561045557600080fd5b50356115e2565b604080516001600160a01b039e8f1681529c8e1660208e01528c81019b909b5260608c019990995296151560808b0152948a1660a08a015260c089019390935260e088019190915261010087015261012086015261014085015215156101608401529092166101808201529051908190036101a00190f35b3480156104e057600080fd5b50610379600480360360408110156104f757600080fd5b50803590602001356001600160a01b0316611655565b34801561051957600080fd5b506102cc6004803603602081101561053057600080fd5b5035611a1f565b34801561054357600080fd5b506102cc6004803603604081101561055a57600080fd5b506001600160a01b038135169060200135611aa7565b34801561057c57600080fd5b5061032e611b0b565b34801561059157600080fd5b50610379600480360360408110156105a857600080fd5b50803590602001356001600160a01b0316611b11565b3480156105ca57600080fd5b506102cc600480360360408110156105e157600080fd5b5080359060200135611f73565b3480156105fa57600080fd5b506102cc6004803603604081101561061157600080fd5b506001600160a01b0381351690602001351515612009565b34801561063557600080fd5b506103796004803603602081101561064c57600080fd5b50356001600160a01b03166120b6565b34801561066857600080fd5b5061032e6120cb565b34801561067d57600080fd5b506103796120d1565b34801561069257600080fd5b506102e86120da565b3480156106a757600080fd5b506102cc600480360360608110156106be57600080fd5b50803590602081013590604001356120e9565b3480156106dd57600080fd5b5061032e600480360360208110156106f457600080fd5b503561218a565b34801561070757600080fd5b506102cc6004803603602081101561071e57600080fd5b5035612197565b34801561073157600080fd5b5061032e61221f565b61032e600480360361012081101561075157600080fd5b810190602081018135600160201b81111561076b57600080fd5b82018360208201111561077d57600080fd5b803590602001918460208302840111600160201b8311171561079e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b03908116935060208301359260408101359250606081013515159160808201359160a08101359160c08201359160e0013516612225565b34801561081f57600080fd5b5061083d6004803603602081101561083657600080fd5b50356123e1565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610879578181015183820152602001610861565b505050509050019250505060405180910390f35b34801561089957600080fd5b50610379600480360360408110156108b057600080fd5b50803590602001356001600160a01b0316612517565b3480156108d257600080fd5b5061032e600480360360408110156108e957600080fd5b506001600160a01b0381351690602001356128b5565b34801561090b57600080fd5b5061032e6004803603602081101561092257600080fd5b50356001600160a01b03166128fb565b34801561093e57600080fd5b5061032e61290d565b34801561095357600080fd5b506102e8612913565b34801561096857600080fd5b5061032e612922565b34801561097d57600080fd5b506102cc6004803603602081101561099457600080fd5b50356001600160a01b0316612928565b61032e60048036036101608110156109bb57600080fd5b810190602081018135600160201b8111156109d557600080fd5b8201836020820111156109e757600080fd5b803590602001918460208302840111600160201b83111715610a0857600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b039081169350602083013592604081013592506060810135151591608082013581169160a08101359160c08201359160e0810135916101008201351515916101200135166129c9565b348015610a9b57600080fd5b506102cc60048036036020811015610ab257600080fd5b50351515612b79565b348015610ac757600080fd5b506102cc60048036036020811015610ade57600080fd5b5035612c0d565b348015610af157600080fd5b5061083d60048036036020811015610b0857600080fd5b5035612c95565b348015610b1b57600080fd5b5061032e60048036036020811015610b3257600080fd5b50356001600160a01b0316612dc3565b348015610b4e57600080fd5b5061032e612dd5565b348015610b6357600080fd5b506102cc60048036036040811015610b7a57600080fd5b50803590602001356001600160a01b0316612ddb565b348015610b9c57600080fd5b5061032e612fb3565b348015610bb157600080fd5b506102cc60048036036020811015610bc857600080fd5b50356001600160a01b0316612fb9565b348015610be457600080fd5b5061032e61305a565b348015610bf957600080fd5b506102cc60048036036020811015610c1057600080fd5b50356001600160a01b0316613060565b348015610c2c57600080fd5b506102cc60048036036060811015610c4357600080fd5b506001600160a01b038135811691602081013590911690604001356130cf565b348015610c6f57600080fd5b506102cc60048036036060811015610c8657600080fd5b810190602081018135600160201b811115610ca057600080fd5b820183602082011115610cb257600080fd5b803590602001918460208302840111600160201b83111715610cd357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610d2257600080fd5b820183602082011115610d3457600080fd5b803590602001918460208302840111600160201b83111715610d5557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610da457600080fd5b820183602082011115610db657600080fd5b803590602001918460208302840111600160201b83111715610dd757600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506131a4945050505050565b348015610e2157600080fd5b506102cc60048036036040811015610e3857600080fd5b506001600160a01b0381358116916020013516613393565b61032e6004803603610140811015610e6757600080fd5b810190602081018135600160201b811115610e8157600080fd5b820183602082011115610e9357600080fd5b803590602001918460208302840111600160201b83111715610eb457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b038335811694506020840135936040810135935060608101359250608081013515159160a08201359160c08101359160e082013591610100013516613477565b348015610f3b57600080fd5b50610f4461365a565b604080519485526020850193909352838301919091526060830152519081900360800190f35b348015610f7657600080fd5b506102cc60048036036040811015610f8d57600080fd5b50803590602001356001600160a01b031661366c565b348015610faf57600080fd5b506102e8613844565b348015610fc457600080fd5b506102e8613853565b348015610fd957600080fd5b5061032e60048036036020811015610ff057600080fd5b50356001600160a01b0316613862565b34801561100c57600080fd5b5061102a6004803603602081101561102357600080fd5b5035613874565b604080516001600160a01b039d8e1681529b8d1660208d01528b81019a909a5260608b019890985260808a019690965293151560a089015260c088019290925260e087015261010086015261012085015215156101408401529092166101608201529051908190036101800190f35b3480156110a557600080fd5b506102e86138e8565b3480156110ba57600080fd5b506102cc600480360360208110156110d157600080fd5b50356138f7565b6009546001600160a01b031681565b601281815481106110f457fe5b600091825260209091200154905081565b60006002600054141561114d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b600260005561115a615b85565b6000848152601b602090815260409182902082516101c08101845281546001600160a01b03168152600182018054855181860281018601909652808652919492938581019392908301828280156111da57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116111bc575b505050918352505060028201546001600160a01b0390811660208301526003830154604083015260048301546060830152600583015460ff8082161515608085015261010091829004831660a0850152600685015460c0850152600785015460e08501526008850154828501526009850154610120850152600a850154610140850152600b90940154938416151561016084015290920482166101809091015281519192501661128e576001915050611598565b60006112a9826101400151836101600151846000015161397f565b9050806112bb57600092505050611598565b6000858152601b6020526040812080546001600160a01b0319168155906112e56001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820180546001600160a81b031990811690915560068301829055600783018290556008830182905560098301829055600a8301829055600b909201805490921690915582516020840151805161138b929190849061136457fe5b60200260200101518560400151866060015187608001518860a00151308a60e00151613b05565b9050801561143a57600183602001515111156113fb57600354602084015180516113e4926001600160a01b03169184916000906113c457fe5b60200260200101516001600160a01b0316613f9e9092919063ffffffff16565b6113f8836020015184610100015130613ff5565b90505b8261018001511561141957611414818460c00151614073565b61143a565b61143a8360c00151828560200151600187602001515103815181106113c457fe5b61144983610120015186614073565b82600001516001600160a01b03167f21435c5b618d77ff3657140cd3318e2cffaebc5e0e1b7318f56a9ba4044c3ed284602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001516114bf8e61014001514361410190919063ffffffff16565b6101608f01516114d0904290614101565b60405180806020018c6001600160a01b031681526020018b81526020018a81526020018915158152602001886001600160a01b0316815260200187815260200186815260200185815260200184815260200183815260200182810382528d818151815260200191508051906020019060200280838360005b83811015611560578181015183820152602001611548565b505050509050019c5050505050505050505050505060405180910390a2611590836101a001518760016000614143565b600193505050505b600160005592915050565b600b6020526000908152604090205481565b61271081565b6001546001600160a01b031681565b60155481565b600a6020526000908152604090205481565b601b602052600090815260409020805460028201546003830154600484015460058501546006860154600787015460088801546009890154600a8a0154600b909a01546001600160a01b03998a169a988a16999798969760ff80881698610100988990048316989093918216929104168d565b60006002600054141561169d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b60026000556116aa615c3a565b60008481526019602090815260409182902082516101a08101845281546001600160a01b031681526001820180548551818602810186019096528086529194929385810193929083018282801561172a57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161170c575b505050918352505060028201546001600160a01b039081166020830152600383015460408301526004830154606083015260058301546080830152600683015460ff908116151560a0840152600784015460c0840152600884015460e0840152600984015461010080850191909152600a850154610120850152600b909401549081161515610140840152929092048216610160909101528151919250166117d6576001915050611598565b60006117f18261012001518361014001518460000151614249565b90508061180357600092505050611598565b600085815260196020526040812080546001600160a01b03191681559061182d6001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820181905560068201805460ff19169055600782018190556008820181905560098201819055600a820155600b0180546001600160a81b0319169055610160820151156118b0576118ab82606001518360000151614073565b6118ce565b6118ce8260000151836060015184602001516000815181106113c457fe5b6118dd82610100015185614073565b81600001516001600160a01b03167f35b638e650e2328786fb405bd69d2083dbedc018d086662e74b775b4f1dae4bf83602001518460400151856060015186608001518760a001518860c001518960e001518a610100015161194d8c61012001514361410190919063ffffffff16565b6101408d015161195e904290614101565b60405180806020018b6001600160a01b031681526020018a8152602001898152602001888152602001871515815260200186815260200185815260200184815260200183815260200182810382528c818151815260200191508051906020019060200280838360005b838110156119df5781810151838201526020016119c7565b505050509050019b50505050505050505050505060405180910390a2611a0e8261018001518660006001614143565b600192505050600160005592915050565b6002546001600160a01b03163314611a6c576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60088190556040805182815290517f21167d0d4661af93817ebce920f18986eed3d75d5e1c03f2aed05efcbafbc4529181900360200190a150565b6001546001600160a01b03163314611af4576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b611b076001600160a01b038316826142b1565b5050565b60165481565b600060026000541415611b59576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055611b66615c3a565b60008481526019602090815260409182902082516101a08101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015611be657602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611bc8575b505050918352505060028201546001600160a01b039081166020830152600383015460408301526004830154606083015260058301546080830152600683015460ff908116151560a0840152600784015460c0840152600884015460e0840152600984015461010080850191909152600a850154610120850152600b90940154908116151561014084015292909204821661016090910152815191925016611c92576001915050611598565b6000611cad826101200151836101400151846000015161397f565b905080611cbf57600092505050611598565b600085815260196020526040812080546001600160a01b031916815590611ce96001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820181905560068201805460ff19169055600782018190556008820181905560098201819055600a820155600b0180546001600160a81b0319169055606082015115611df657606082015160208301515160011015611da557600354606084015160208501518051611d8f936001600160a01b03169291906000906113c457fe5b611da28360200151846080015130613ff5565b90505b6000611dc53385602001518487604001518860c001518960a00151614396565b60035460208601518051929350611df3926001600160a01b039092169184919060001981019081106113c457fe5b50505b815160208301518051611e349291906000198101908110611e1357fe5b602002602001015184604001518560a001518660c001518760e00151614475565b611e4382610100015185614073565b81600001516001600160a01b03167f1be316b94d38c07bd41cdb4913772d0a0a82802786a2f8b657b6e85dbcdfc64183602001518460400151856060015186608001518760a001518860c001518960e001518a6101000151611eb38c61012001514361410190919063ffffffff16565b6101408d0151611ec4904290614101565b60405180806020018b6001600160a01b031681526020018a8152602001898152602001888152602001871515815260200186815260200185815260200184815260200183815260200182810382528c818151815260200191508051906020019060200280838360005b83811015611f45578181015183820152602001611f2d565b505050509050019b50505050505050505050505060405180910390a2611a0e82610180015186600180614143565b6002546001600160a01b03163314611fc0576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60148290556015819055604080518381526020810183905281517febb0f666150f4be5b60c45df8f3e49992510b0128027fe58eea6110f296493bc929181900390910190a15050565b6002546001600160a01b03163314612056576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6001600160a01b038216600081815260176020908152604091829020805460ff1916851515908117909155825190815291517ffbabc02389290a451c6e600d05bf9887b99bfad39d8e1237e4e3df042e4941fe9281900390910190a25050565b60176020526000908152604090205460ff1681565b600f5481565b60115460ff1681565b6006546001600160a01b031681565b6002546001600160a01b03163314612136576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600e839055600f8290556010819055604080518481526020810184905280820183905290517fb98e759701eaca2e60c25e91109003c1c7442ef731b5d569037063005da8254d9181900360600190a1505050565b601381815481106110f457fe5b6002546001600160a01b031633146121e4576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60078190556040805182815290517f974fd3c1fcb4653dfc4fb740c4c692cd212d55c28f163f310128cb64d83006759181900360200190a150565b600e5481565b60006002600054141561226d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d548410156122af576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b833410156122ea576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8951600114806122fb575089516002145b612332576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b6006548a516001600160a01b03909116908b9060009061234e57fe5b60200260200101516001600160a01b03161461239a576040805162461bcd60e51b815260206004808301919091526024820152630e0c2e8d60e31b604482015290519081900360640190fd5b6123a261487d565b6123ab836148e9565b60006123b73486614101565b90506123cd338c8c848d8d8d8d8d60018d61495d565b60016000559b9a5050505050505050505050565b60606123eb615b85565b6000838152601b602090815260409182902082516101c08101845281546001600160a01b031681526001820180548551818602810186019096528086529194929385810193929083018282801561246b57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161244d575b505050918352505060028201546001600160a01b039081166020808401919091526003840154604084015260048401546060840152600584015460ff8082161515608086015261010091829004841660a0860152600686015460c0860152600786015460e08601526008860154828601526009860154610120860152600a860154610140860152600b909501549485161515610160850152909304166101809091015201519392505050565b60006002600054141561255f576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b600260005561256c615b85565b6000848152601b602090815260409182902082516101c08101845281546001600160a01b03168152600182018054855181860281018601909652808652919492938581019392908301828280156125ec57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116125ce575b505050918352505060028201546001600160a01b0390811660208301526003830154604083015260048301546060830152600583015460ff8082161515608085015261010091829004831660a0850152600685015460c0850152600785015460e08501526008850154828501526009850154610120850152600a850154610140850152600b9094015493841615156101608401529092048216610180909101528151919250166126a0576001915050611598565b60006126bb8261014001518361016001518460000151614249565b9050806126cd57600092505050611598565b6000858152601b6020526040812080546001600160a01b0319168155906126f76001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820180546001600160a81b031990811690915560068301829055600783018290556008830182905560098301829055600a830191909155600b9091018054909116905561012082015161276f9085614073565b81600001516001600160a01b03167f87abfd78e844f28318363bdf3da99eab2f4a2da9ff7ae365484507f7b6c3f80583602001518460400151856060015186608001518760a001518860c001518960e001518a61010001518b61012001516127e58d61014001514361410190919063ffffffff16565b6101608e01516127f6904290614101565b60405180806020018c6001600160a01b031681526020018b81526020018a81526020018915158152602001886001600160a01b0316815260200187815260200186815260200185815260200184815260200183815260200182810382528d818151815260200191508051906020019060200280838360005b8381101561288657818101518382015260200161286e565b505050509050019c5050505050505050505050505060405180910390a2611a0e826101a0015186600080614143565b6000828260405160200180836001600160a01b031660601b8152601401828152602001925050506040516020818303038152906040528051906020012090505b92915050565b60186020526000908152604090205481565b600d5481565b6004546001600160a01b031681565b60075481565b6001546001600160a01b03163314612975576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f5a272403b402d892977df56625f4164ccaf70ca3863991c43ecfe76a6905b0a19181900360200190a150565b600060026000541415612a11576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d54841015612a53576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b833414612a8d576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8b5160011480612a9e57508b516002145b612ad5576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b8215612b48576006548c516001600160a01b03909116908d906000198101908110612afc57fe5b60200260200101516001600160a01b031614612b48576040805162461bcd60e51b815260206004808301919091526024820152630e0c2e8d60e31b604482015290519081900360640190fd5b612b5061487d565b612b64338d8d8d8d8d8d8d8d8d8d8d614af4565b60016000559c9b505050505050505050505050565b6002546001600160a01b03163314612bc6576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6011805482151560ff19909116811790915560408051918252517f4eb87a5935d402aa24c01b45bfb30adefcd2328b480f2d967864de4b64ea929f9181900360200190a150565b6002546001600160a01b03163314612c5a576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60168190556040805182815290517f22bd2c9f980325d046be74aaef5fc76df4a2bc3fbc7c5a1200fcc79fe80dab6c9181900360200190a150565b6060612c9f615c3a565b60008381526019602090815260409182902082516101a08101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015612d1f57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612d01575b505050918352505060028201546001600160a01b03908116602080840191909152600384015460408401526004840154606084015260058401546080840152600684015460ff908116151560a0850152600785015460c0850152600885015460e0850152600985015461010080860191909152600a860154610120860152600b90950154908116151561014085015293909304166101609091015201519392505050565b600c6020526000908152604090205481565b60085481565b3360009081526017602052604090205460ff16612e25576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b601454601254808210612e39575050611b07565b80841115612e45578093505b83821015612fab57600060128381548110612e5c57fe5b90600052602060002001549050306001600160a01b03166327b42c0f82866040518363ffffffff1660e01b815260040180838152602001826001600160a01b0316815260200192505050602060405180830381600087803b158015612ec057600080fd5b505af1925050508015612ee557506040513d6020811015612ee057600080fd5b505160015b612f79576040805163225fc9fd60e01b8152600481018390526001600160a01b03861660248201529051309163225fc9fd9160448083019260209291908290030181600087803b158015612f3857600080fd5b505af1925050508015612f5d57506040513d6020811015612f5857600080fd5b505160015b612f6657612f74565b80612f72575050612fab565b505b612f87565b80612f85575050612fab565b505b60128381548110612f9457fe5b600091825260208220015550600190910190612e45565b506014555050565b60145481565b6002546001600160a01b03163314613006576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600980546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f828abcccea18192c21d645e575652c49e20b986dab777906fc473d056b01b6a89181900360200190a150565b60105481565b6001546001600160a01b031633146130ad576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6001546001600160a01b0316331461311c576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b826001600160a01b031663095ea7b383836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b15801561317357600080fd5b505af1158015613187573d6000803e3d6000fd5b505050506040513d602081101561319d57600080fd5b5050505050565b6002546001600160a01b031633146131f1576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60005b835181101561328c57600084828151811061320b57fe5b6020026020010151905083828151811061322157fe5b6020026020010151600b6000836001600160a01b03166001600160a01b031681526020019081526020016000208190555082828151811061325e57fe5b6020908102919091018101516001600160a01b039092166000908152600c90915260409020556001016131f4565b507fae32d569b058895b9620d6552b09aaffedc9a6f396be4d595a224ad09f8b213983838360405180806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b838110156132f95781810151838201526020016132e1565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015613338578181015183820152602001613320565b50505050905001848103825285818151815260200191508051906020019060200280838360005b8381101561337757818101518382015260200161335f565b50505050905001965050505050505060405180910390a1505050565b6002546001600160a01b031633146133e0576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6001600160a01b0382166000908152600a6020526040902054806134045750611b07565b6001600160a01b0383166000818152600a6020526040812055613428908383613f9e565b604080516001600160a01b0380861682528416602082015280820183905290517f4f1b51dd7a2fcb861aa2670f668be66835c4ee12b4bbbf037e4d0018f39819e49181900360600190a1505050565b6000600260005414156134bf576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d54841015613501576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b83341461353b576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8a516001148061354c57508a516002145b613583576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b61358b61487d565b613594836148e9565b8815613646576005548b516001600160a01b0390911690631b827878908d906000906135bc57fe5b602002602001015133308d6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b15801561362d57600080fd5b505af1158015613641573d6000803e3d6000fd5b505050505b6123cd338c8c8c8c8c8c8c8c60008c61495d565b60145460125460155460135490919293565b3360009081526017602052604090205460ff166136b6576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b6015546013548082106136ca575050611b07565b808411156136d6578093505b8382101561383c576000601383815481106136ed57fe5b90600052602060002001549050306001600160a01b0316630d4d003d82866040518363ffffffff1660e01b815260040180838152602001826001600160a01b0316815260200192505050602060405180830381600087803b15801561375157600080fd5b505af192505050801561377657506040513d602081101561377157600080fd5b505160015b61380a5760408051633051b17160e11b8152600481018390526001600160a01b0386166024820152905130916360a362e29160448083019260209291908290030181600087803b1580156137c957600080fd5b505af19250505080156137ee57506040513d60208110156137e957600080fd5b505160015b6137f757613805565b8061380357505061383c565b505b613818565b8061381657505061383c565b505b6013838154811061382557fe5b6000918252602082200155506001909101906136d6565b506015555050565b6002546001600160a01b031681565b6005546001600160a01b031681565b601a6020526000908152604090205481565b6019602052600090815260409020805460028201546003830154600484015460058501546006860154600787015460088801546009890154600a8a0154600b909a01546001600160a01b03998a169a988a169997989697959660ff958616969495939492939092908216916101009004168c565b6003546001600160a01b031681565b6002546001600160a01b03163314613944576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600d8190556040805182815290517f52a8358457e20bbb36e4086b83fb0749599f1893fe4c35a876c46dc4886d12db9181900360200190a150565b60004261399760105485614cce90919063ffffffff16565b116139d3576040805162461bcd60e51b8152602060048201526007602482015266195e1c1a5c995960ca1b604482015290519081900360640190fd5b6000333014806139f257503360009081526017602052604090205460ff165b60115490915060ff16158015613a06575080155b15613a3e576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b8015613a645743613a5a600e5487614cce90919063ffffffff16565b1115915050613afe565b336001600160a01b03841614613aa7576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b42613abd600f5486614cce90919063ffffffff16565b1115613af8576040805162461bcd60e51b815260206004820152600560248201526464656c617960d81b604482015290519081900360640190fd5b60019150505b9392505050565b6003546000906001600160a01b03168185613b9857816001600160a01b031663e124e6d28a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613b6757600080fd5b505afa158015613b7b573d6000803e3d6000fd5b505050506040513d6020811015613b9157600080fd5b5051613c12565b816001600160a01b03166381a612d68a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613be557600080fd5b505afa158015613bf9573d6000803e3d6000fd5b505050506040513d6020811015613c0f57600080fd5b50515b90508515613c5e5783811015613c595760405162461bcd60e51b8152600401808060200182810382526030815260200180615e546030913960400191505060405180910390fd5b613c9d565b83811115613c9d5760405162461bcd60e51b8152600401808060200182810382526031815260200180615ef46031913960400191505060405180910390fd5b6000826001600160a01b03166312d43a516040518163ffffffff1660e01b815260040160206040518083038186803b158015613cd857600080fd5b505afa158015613cec573d6000803e3d6000fd5b505050506040513d6020811015613d0257600080fd5b81019080805190602001909291905050509050600460009054906101000a90046001600160a01b03166001600160a01b031663f3238cec8d8d8d8b8d8860006040518863ffffffff1660e01b815260040180886001600160a01b03168152602001876001600160a01b03168152602001866001600160a01b0316815260200185151581526020018481526020018381526020018215158152602001975050505050505050600060405180830381600087803b158015613dc057600080fd5b505af1158015613dd4573d6000803e3d6000fd5b50505050806001600160a01b0316636d63c1d0846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b158015613e2757600080fd5b505af1158015613e3b573d6000803e3d6000fd5b505050506000600560009054906101000a90046001600160a01b03166001600160a01b0316632662166b8e8e8e8e8e8e8e6040518863ffffffff1660e01b815260040180886001600160a01b03168152602001876001600160a01b03168152602001866001600160a01b031681526020018581526020018481526020018315158152602001826001600160a01b03168152602001975050505050505050602060405180830381600087803b158015613ef257600080fd5b505af1158015613f06573d6000803e3d6000fd5b505050506040513d6020811015613f1c57600080fd5b50516040805163d3c87bbb60e01b81526001600160a01b03878116600483015291519293509084169163d3c87bbb9160248082019260009290919082900301818387803b158015613f6c57600080fd5b505af1158015613f80573d6000803e3d6000fd5b50505050613f8e8d8a614d26565b9c9b505050505050505050505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052613ff0908490614eaf565b505050565b600083516002141561403c576140358460008151811061401157fe5b60200260200101518560018151811061402657fe5b60200260200101518585614f60565b9050613afe565b60405162461bcd60e51b8152600401808060200182810382526029815260200180615f256029913960400191505060405180910390fd5b60065460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156140c057600080fd5b505af11580156140d4573d6000803e3d6000fd5b50506040516001600160a01b038416925084156108fc02915084906000818181858888f150505050505050565b6000613afe83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250615036565b6001600160a01b03841661415657614243565b614168846001600160a01b03166150cd565b61417157614243565b6016548061417f5750614243565b6000856001600160a01b031663edf3daec838787876040518563ffffffff1660e01b815260040180848152602001831515815260200182151581526020019350505050600060405180830381600088803b1580156141dc57600080fd5b5087f1935050505080156141ee575060015b6141f7576141fb565b5060015b604080516001600160a01b0388168152821515602082015281517f46ddbd62fc1a7626fe9c43026cb0694aec0b031fe81ac66fb4cfe9381dc6fe72929181900390910190a150505b50505050565b600080333014806139f257503360009081526017602052604090205460ff1660115490915060ff16158015613a06575080613a3e576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b80471015614306576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114614351576040519150601f19603f3d011682016040523d82523d6000602084013e614356565b606091505b5050905080613ff05760405162461bcd60e51b815260040180806020018281038252603a815260200180615df4603a913960400191505060405180910390fd5b6000806143a78888888888886150d3565b905080156144665760006143de6127106143d86143d160075461271061410190919063ffffffff16565b8a906152b0565b90615309565b905060006143ec8883614101565b905060008960018b51038151811061440057fe5b6020026020010151905061444282600a6000846001600160a01b03166001600160a01b0316815260200190815260200160002054614cce90919063ffffffff16565b6001600160a01b039091166000908152600a602052604090205550915061446b9050565b859150505b9695505050505050565b6003546001600160a01b031660008361450657816001600160a01b03166381a612d6876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156144d557600080fd5b505afa1580156144e9573d6000803e3d6000fd5b505050506040513d60208110156144ff57600080fd5b5051614580565b816001600160a01b031663e124e6d2876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561455357600080fd5b505afa158015614567573d6000803e3d6000fd5b505050506040513d602081101561457d57600080fd5b50515b905083156145cc57828111156145c75760405162461bcd60e51b8152600401808060200182810382526031815260200180615ef46031913960400191505060405180910390fd5b61460b565b8281101561460b5760405162461bcd60e51b8152600401808060200182810382526030815260200180615e546030913960400191505060405180910390fd5b614616868587615348565b6000826001600160a01b03166312d43a516040518163ffffffff1660e01b815260040160206040518083038186803b15801561465157600080fd5b505afa158015614665573d6000803e3d6000fd5b505050506040513d602081101561467b57600080fd5b50516004805460408051633cc8e33b60e21b81526001600160a01b038e8116948201949094528c841660248201528b841660448201528915156064820152608481018b905260a48101879052600160c4820152905193945091169163f3238cec9160e48082019260009290919082900301818387803b1580156146fd57600080fd5b505af1158015614711573d6000803e3d6000fd5b50505050806001600160a01b0316636d63c1d0846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b15801561476457600080fd5b505af1158015614778573d6000803e3d6000fd5b505060055460408051630f8ee8bb60e11b81526001600160a01b038e811660048301528d811660248301528c81166044830152606482018c90528a151560848301529151919092169350631f1dd176925060a480830192600092919082900301818387803b1580156147e957600080fd5b505af11580156147fd573d6000803e3d6000fd5b50505050806001600160a01b031663d3c87bbb846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b15801561485057600080fd5b505af1158015614864573d6000803e3d6000fd5b5050505061487289876154fa565b505050505050505050565b34156148e757600660009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156148d357600080fd5b505af115801561319d573d6000803e3d6000fd5b565b801580159061490257506009546001600160a01b031615155b1561495a57600954604080516356b4b2ad60e01b81523360048201526024810184905290516001600160a01b03909216916356b4b2ad9160448082019260009290919082900301818387803b1580156148d357600080fd5b50565b6000614967615c3a565b604051806101a001604052808e6001600160a01b031681526020018d81526020018c6001600160a01b031681526020018b81526020018a815260200189815260200188151581526020018781526020018681526020014381526020014281526020018515158152602001846001600160a01b031681525090506000806149ec83615600565b915091508e6001600160a01b03167f5265bc4952da402633b3fc35f67ab4245493a0ab94dd8ab123667c8d45a4485c8f8f8f8f8f8f8f8f8b60016012805490500343423a60405180806020018e6001600160a01b031681526020018d81526020018c81526020018b81526020018a1515815260200189815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528f818151815260200191508051906020019060200280838360005b83811015614ac3578181015183820152602001614aab565b505050509050019e50505050505050505050505050505060405180910390a29e9d5050505050505050505050505050565b6000614afe615b85565b604051806101c001604052808f6001600160a01b031681526020018e81526020018d6001600160a01b031681526020018c81526020018b81526020018a15158152602001896001600160a01b031681526020018881526020018781526020018681526020014381526020014281526020018515158152602001846001600160a01b03168152509050600080614b928361579e565b9150915082600001516001600160a01b03167f81ed0476a7e785a9e4728fffd679ea97176ca1ac85e1003462558bb5677da57b84602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001518c600160138054905003434260405180806020018e6001600160a01b031681526020018d81526020018c81526020018b151581526020018a6001600160a01b0316815260200189815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528f818151815260200191508051906020019060200280838360005b83811015614c9c578181015183820152602001614c84565b505050509050019e50505050505050505050505050505060405180910390a29f9e505050505050505050505050505050565b600082820183811015613afe576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6009546001600160a01b031680614d3d5750611b07565b600080826001600160a01b031663534ef883866040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050604080518083038186803b158015614d8c57600080fd5b505afa158015614da0573d6000803e3d6000fd5b505050506040513d6040811015614db657600080fd5b508051602090910151909250905081614dd157505050611b07565b7f474c763ff84bf2c2039a6d9fea955ecd0f724030e3c365b91169c6a16fe751b78585600360009054906101000a90046001600160a01b03166001600160a01b031663318bc6896040518163ffffffff1660e01b815260040160206040518083038186803b158015614e4257600080fd5b505afa158015614e56573d6000803e3d6000fd5b505050506040513d6020811015614e6c57600080fd5b5051604080516001600160a01b03948516815260208101939093528281019190915260608201869052918416608082015290519081900360a00190a15050505050565b6060614f04826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166159479092919063ffffffff16565b805190915015613ff057808060200190516020811015614f2357600080fd5b5051613ff05760405162461bcd60e51b815260040180806020018281038252602a815260200180615f71602a913960400191505060405180910390fd5b60035460408051634998b10960e11b81526001600160a01b03878116600483015286811660248301528481166044830152915160009384931691639331621291606480830192602092919082900301818787803b158015614fc057600080fd5b505af1158015614fd4573d6000803e3d6000fd5b505050506040513d6020811015614fea57600080fd5b505190508381101561502d5760405162461bcd60e51b815260040180806020018281038252602b815260200180615dc9602b913960400191505060405180910390fd5b95945050505050565b600081848411156150c55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561508a578181015183820152602001615072565b50505050905090810190601f1680156150b75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b3b151590565b6000826150e25750600061446b565b816150ef5750600161446b565b60008660018851038151811061510157fe5b602090810291909101015160035460408051634a3f088d60e01b81526001600160a01b038c81166004830152808516602483015289811660448301528815156064830152915193945091169160009182918491634a3f088d91608480830192610100929190829003018186803b15801561517a57600080fd5b505afa15801561518e573d6000803e3d6000fd5b505050506040513d6101008110156151a557600080fd5b5080516020909101519092509050816151c557600094505050505061446b565b60006151d18388614cce565b90506000846001600160a01b0316630a48d5a9878d6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b15801561522a57600080fd5b505afa15801561523e573d6000803e3d6000fd5b505050506040513d602081101561525457600080fd5b5051905060006152648483614cce565b90506000615278856143d8886127106152b0565b90506000615299836143d860085461271001886152b090919063ffffffff16565b919091109f9e505050505050505050505050505050565b6000826152bf575060006128f5565b828202828482816152cc57fe5b0414613afe5760405162461bcd60e51b8152600401808060200182810382526021815260200180615ed36021913960400191505060405180910390fd5b6000613afe83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b81525061595e565b8061535257613ff0565b8115615445576001600160a01b0383166000908152600b6020526040902054801580159061540357506003546040805163783a2b6760e11b81526001600160a01b0387811660048301529151849361540193879391169163f07456ce91602480820192602092909190829003018186803b1580156153cf57600080fd5b505afa1580156153e3573d6000803e3d6000fd5b505050506040513d60208110156153f957600080fd5b505190614cce565b115b1561543f5760405162461bcd60e51b815260040180806020018281038252602e815260200180615d5b602e913960400191505060405180910390fd5b50613ff0565b6001600160a01b0383166000908152600c602052604090205480158015906154be57506003546040805163114f1b5560e31b81526001600160a01b038781166004830152915184936154bc938793911691638a78daa891602480820192602092909190829003018186803b1580156153cf57600080fd5b115b156142435760405162461bcd60e51b815260040180806020018281038252602f815260200180615ea4602f913960400191505060405180910390fd5b6009546001600160a01b0316806155115750611b07565b600080826001600160a01b031663534ef883866040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050604080518083038186803b15801561556057600080fd5b505afa158015615574573d6000803e3d6000fd5b505050506040513d604081101561558a57600080fd5b5080516020918201516003546040805163318bc68960e01b815290519396509194507fc2414023ce7002ee98557d1e7be21e5559073336f2217ee5f9b2e50fd85f71ee93899389936001600160a01b039093169263318bc689926004808301939192829003018186803b158015614e4257600080fd5b80516001600160a01b03811660009081526018602052604081205490918291829061562c906001614cce565b6001600160a01b038316600090815260186020526040812082905590915061565483836128b5565b6000818152601960209081526040909120885181546001600160a01b0319166001600160a01b03909116178155888201518051939450899391926156a092600185019290910190615cc1565b5060408201516002820180546001600160a01b039283166001600160a01b0319909116179055606083015160038301556080830151600483015560a0830151600583015560c083015160068301805491151560ff1992831617905560e084015160078401556101008085015160088501556101208501516009850155610140850151600a850155610160850151600b90940180546101809096015190931602610100600160a81b0319931515949091169390931791909116919091179055601280546001810182556000919091527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344401819055909350915050915091565b80516001600160a01b0381166000908152601a60205260408120549091829182906157ca906001614cce565b6001600160a01b0383166000908152601a602052604081208290559091506157f283836128b5565b6000818152601b60209081526040909120885181546001600160a01b0319166001600160a01b039091161781558882015180519394508993919261583e92600185019290910190615cc1565b5060408201516002820180546001600160a01b039283166001600160a01b0319909116179055606083015160038301556080830151600483015560a083015160058301805460c08601518416610100908102610100600160a81b031994151560ff199384161785161790925560e0860151600686015581860151600786015561012086015160088601556101408601516009860155610160860151600a860155610180860151600b90950180546101a090970151909416909102931515941693909317909216179055601380546001810182556000919091527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09001819055909350915050915091565b606061595684846000856159c3565b949350505050565b600081836159ad5760405162461bcd60e51b815260206004820181815283516024840152835190928392604490910191908501908083836000831561508a578181015183820152602001615072565b5060008385816159b957fe5b0495945050505050565b606082471015615a045760405162461bcd60e51b8152600401808060200182810382526026815260200180615e2e6026913960400191505060405180910390fd5b615a0d856150cd565b615a5e576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b60208310615a9d5780518252601f199092019160209182019101615a7e565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114615aff576040519150601f19603f3d011682016040523d82523d6000602084013e615b04565b606091505b5091509150615b14828286615b1f565b979650505050505050565b60608315615b2e575081613afe565b825115615b3e5782518084602001fd5b60405162461bcd60e51b815260206004820181815284516024840152845185939192839260440191908501908083836000831561508a578181015183820152602001615072565b604051806101c0016040528060006001600160a01b031681526020016060815260200160006001600160a01b03168152602001600081526020016000815260200160001515815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081526020016000815260200160001515815260200160006001600160a01b031681525090565b508054600082559060005260206000209081019061495a9190615d26565b604051806101a0016040528060006001600160a01b031681526020016060815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000151581526020016000815260200160008152602001600081526020016000815260200160001515815260200160006001600160a01b031681525090565b828054828255906000526020600020908101928215615d16579160200282015b82811115615d1657825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190615ce1565b50615d22929150615d3b565b5090565b5b80821115615d225760008155600101615d27565b5b80821115615d225780546001600160a01b0319168155600101615d3c56fe42617365506f736974696f6e4d616e616765723a206d617820676c6f62616c206c6f6e67732065786365656465645265656e7472616e637947756172643a207265656e7472616e742063616c6c00476f7665726e61626c653a20666f7262696464656e000000000000000000000042617365506f736974696f6e4d616e616765723a20696e73756666696369656e7420616d6f756e744f7574416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c42617365506f736974696f6e4d616e616765723a206d61726b207072696365206c6f776572207468616e206c696d697442617365506f736974696f6e4d616e616765723a20666f7262696464656e000042617365506f736974696f6e4d616e616765723a206d617820676c6f62616c2073686f727473206578636565646564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7742617365506f736974696f6e4d616e616765723a206d61726b20707269636520686967686572207468616e206c696d697442617365506f736974696f6e4d616e616765723a20696e76616c6964205f706174682e6c656e67746842617365506f736974696f6e4d616e616765723a20696e76616c69642073656e6465725361666545524332303a204552433230206f7065726174696f6e20646964206e6f742073756363656564a26469706673582212209ce5a3b6726e45235a68f6f370dd0252abe63f1c6342dc561eba4b4a5871f32764736f6c634300060c0033000000000000000000000000489ee077994b6658eafa855c308275ead8097c4a000000000000000000000000abbc5f99639c9b6bcb58544ddf04efa6802f406400000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000f58eec83ba28ddd79390b9e90c4d3ebff1d434da000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000005af3107a4000 \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/Reader.abi b/src/Managing.Tools.ABI/Gmx/core/Reader.abi new file mode 100644 index 0000000..91f0034 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/Reader.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"BASIS_POINTS_DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POSITION_PROPS_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"USDG_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IVault","name":"_vault","type":"address"},{"internalType":"address","name":"_tokenIn","type":"address"},{"internalType":"address","name":"_tokenOut","type":"address"},{"internalType":"uint256","name":"_amountIn","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IVault","name":"_vault","type":"address"},{"internalType":"address","name":"_tokenIn","type":"address"},{"internalType":"address","name":"_tokenOut","type":"address"},{"internalType":"uint256","name":"_amountIn","type":"uint256"}],"name":"getFeeBasisPoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getFees","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"uint256","name":"_usdgAmount","type":"uint256"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getFullVaultTokenInfo","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getFundingRates","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IVault","name":"_vault","type":"address"},{"internalType":"address","name":"_tokenIn","type":"address"},{"internalType":"address","name":"_tokenOut","type":"address"}],"name":"getMaxAmountIn","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getPairInfo","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"address[]","name":"_collateralTokens","type":"address[]"},{"internalType":"address[]","name":"_indexTokens","type":"address[]"},{"internalType":"bool[]","name":"_isLong","type":"bool[]"}],"name":"getPositions","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IVaultPriceFeed","name":"_priceFeed","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getPrices","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address[]","name":"_yieldTrackers","type":"address[]"}],"name":"getStakingInfo","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getTokenBalances","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getTokenBalancesWithSupplies","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"address[]","name":"_excludedAccounts","type":"address[]"}],"name":"getTokenSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"_token","type":"address"},{"internalType":"address[]","name":"_accounts","type":"address[]"}],"name":"getTotalBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_yieldTokens","type":"address[]"}],"name":"getTotalStaked","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"uint256","name":"_usdgAmount","type":"uint256"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getVaultTokenInfo","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"uint256","name":"_usdgAmount","type":"uint256"},{"internalType":"address[]","name":"_tokens","type":"address[]"}],"name":"getVaultTokenInfoV2","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address[]","name":"_vesters","type":"address[]"}],"name":"getVestingInfo","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gov","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hasMaxGlobalShortSizes","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_hasMaxGlobalShortSizes","type":"bool"}],"name":"setConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gov","type":"address"}],"name":"setGov","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/Reader.bin b/src/Managing.Tools.ABI/Gmx/core/Reader.bin new file mode 100644 index 0000000..e68db87 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/Reader.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50600080546001600160a01b031916331790556159ff80620000336000396000f3fe608060405234801561001057600080fd5b506004361061013e5760003560e01c8063126082cf1461014357806312d43a511461015d57806320542568146101815780632413c8c1146102925780632ac0184c146103335780632e3e3342146103e45780633613d52714610495578063440f018c1461054657806348211934146105a05780637b906e931461065157806386d4d0f514610712578063870d917c146107c35780638e83ca32146107cb578063937a0be81461088c57806395082d251461093d57806395a7535a146109455780639b0183c3146109ff578063a4543ead14610a20578063ad7e549714610ad1578063c6f1d67614610ad9578063cfad57a214610af5578063d7176ca914610b1b578063d802178e14610b70578063dc383cab14610c21578063f3535e6c14610ddf578063fbdb05ca14610e17575b600080fd5b61014b610ec8565b60408051918252519081900360200190f35b610165610ece565b604080516001600160a01b039092168252519081900360200190f35b6102426004803603608081101561019757600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156101d157600080fd5b8201836020820111156101e357600080fd5b803590602001918460208302840111600160201b8311171561020457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610edd945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561027e578181015183820152602001610266565b505050509050019250505060405180910390f35b610242600480360360208110156102a857600080fd5b810190602081018135600160201b8111156102c257600080fd5b8201836020820111156102d457600080fd5b803590602001918460208302840111600160201b831117156102f557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506115eb945050505050565b61014b6004803603604081101561034957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561037357600080fd5b82018360208201111561038557600080fd5b803590602001918460208302840111600160201b831117156103a657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506116e4945050505050565b610242600480360360408110156103fa57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561042457600080fd5b82018360208201111561043657600080fd5b803590602001918460208302840111600160201b8311171561045757600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061180f945050505050565b610242600480360360408110156104ab57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104d557600080fd5b8201836020820111156104e757600080fd5b803590602001918460208302840111600160201b8311171561050857600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611a14945050505050565b6105826004803603608081101561055c57600080fd5b506001600160a01b03813581169160208101358216916040820135169060600135611e4e565b60408051938452602084019290925282820152519081900360600190f35b610242600480360360408110156105b657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156105e057600080fd5b8201836020820111156105f257600080fd5b803590602001918460208302840111600160201b8311171561061357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506123b3945050505050565b6102426004803603608081101561066757600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156106a157600080fd5b8201836020820111156106b357600080fd5b803590602001918460208302840111600160201b831117156106d457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550612867945050505050565b6102426004803603604081101561072857600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561075257600080fd5b82018360208201111561076457600080fd5b803590602001918460208302840111600160201b8311171561078557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613098945050505050565b61014b613198565b610242600480360360808110156107e157600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b81111561081b57600080fd5b82018360208201111561082d57600080fd5b803590602001918460208302840111600160201b8311171561084e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061319d945050505050565b610242600480360360408110156108a257600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156108cc57600080fd5b8201836020820111156108de57600080fd5b803590602001918460208302840111600160201b831117156108ff57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613b1a945050505050565b61014b613caf565b6102426004803603606081101561095b57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561098e57600080fd5b8201836020820111156109a057600080fd5b803590602001918460208302840111600160201b831117156109c157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613cbf945050505050565b610a1e60048036036020811015610a1557600080fd5b5035151561418d565b005b61024260048036036040811015610a3657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a6057600080fd5b820183602082011115610a7257600080fd5b803590602001918460208302840111600160201b83111715610a9357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614202945050505050565b61014b61446e565b610ae1614473565b604080519115158252519081900360200190f35b610a1e60048036036020811015610b0b57600080fd5b50356001600160a01b0316614483565b610b5760048036036080811015610b3157600080fd5b506001600160a01b038135811691602081013582169160408201351690606001356144fc565b6040805192835260208301919091528051918290030190f35b61024260048036036040811015610b8657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610bb057600080fd5b820183602082011115610bc257600080fd5b803590602001918460208302840111600160201b83111715610be357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614bb2945050505050565b610242600480360360a0811015610c3757600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b811115610c6a57600080fd5b820183602082011115610c7c57600080fd5b803590602001918460208302840111600160201b83111715610c9d57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610cec57600080fd5b820183602082011115610cfe57600080fd5b803590602001918460208302840111600160201b83111715610d1f57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610d6e57600080fd5b820183602082011115610d8057600080fd5b803590602001918460208302840111600160201b83111715610da157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614cfe945050505050565b61014b60048036036060811015610df557600080fd5b506001600160a01b0381358116916020810135821691604090910135166150fa565b61014b60048036036040811015610e2d57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610e5757600080fd5b820183602082011115610e6957600080fd5b803590602001918460208302840111600160201b83111715610e8a57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506156c1945050505050565b61271081565b6000546001600160a01b031681565b60606000600a905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f2557600080fd5b505afa158015610f39573d6000803e3d6000fd5b505050506040513d6020811015610f4f57600080fd5b5051855190915060609084026001600160401b0381118015610f7057600080fd5b50604051908082528060200260200182016040528015610f9a578160200160208202803683370190505b50905060005b86518110156115de576000878281518110610fb757fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415610fdf5750885b846001600160a01b03166352f55eed826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561102c57600080fd5b505afa158015611040573d6000803e3d6000fd5b505050506040513d602081101561105657600080fd5b50518351849084890290811061106857fe5b602002602001018181525050846001600160a01b031663c3c7b9e9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156110c157600080fd5b505afa1580156110d5573d6000803e3d6000fd5b505050506040513d60208110156110eb57600080fd5b5051835184906001858a020190811061110057fe5b602002602001018181525050846001600160a01b0316631aa4ace5826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561115957600080fd5b505afa15801561116d573d6000803e3d6000fd5b505050506040513d602081101561118357600080fd5b5051835184906002858a020190811061119857fe5b602002602001018181525050846001600160a01b0316632c668ec1828b6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b1580156111f957600080fd5b505afa15801561120d573d6000803e3d6000fd5b505050506040513d602081101561122357600080fd5b5051835184906003858a020190811061123857fe5b602002602001018181525050846001600160a01b031663ab2f3ad4826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561129157600080fd5b505afa1580156112a5573d6000803e3d6000fd5b505050506040513d60208110156112bb57600080fd5b5051835184906004858a02019081106112d057fe5b602002602001018181525050846001600160a01b03166381a612d6826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561132957600080fd5b505afa15801561133d573d6000803e3d6000fd5b505050506040513d602081101561135357600080fd5b5051835184906005858a020190811061136857fe5b602002602001018181525050846001600160a01b031663e124e6d2826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156113c157600080fd5b505afa1580156113d5573d6000803e3d6000fd5b505050506040513d60208110156113eb57600080fd5b5051835184906006858a020190811061140057fe5b602002602001018181525050846001600160a01b031663f07456ce826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561145957600080fd5b505afa15801561146d573d6000803e3d6000fd5b505050506040513d602081101561148357600080fd5b5051835184906007858a020190811061149857fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918716926356c8c2c192604480840193829003018186803b1580156114f257600080fd5b505afa158015611506573d6000803e3d6000fd5b505050506040513d602081101561151c57600080fd5b5051835184906008858a020190811061153157fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918716926356c8c2c192604480840193829003018186803b15801561158b57600080fd5b505afa15801561159f573d6000803e3d6000fd5b505050506040513d60208110156115b557600080fd5b5051835184906009858a02019081106115ca57fe5b602090810291909101015250600101610fa0565b5098975050505050505050565b60608082516001600160401b038111801561160557600080fd5b5060405190808252806020026020018201604052801561162f578160200160208202803683370190505b50905060005b83518110156116dd57600084828151811061164c57fe5b60200260200101519050806001600160a01b031663817b1cd26040518163ffffffff1660e01b815260040160206040518083038186803b15801561168f57600080fd5b505afa1580156116a3573d6000803e3d6000fd5b505050506040513d60208110156116b957600080fd5b505183518490849081106116c957fe5b602090810291909101015250600101611635565b5092915050565b600080836001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561172057600080fd5b505afa158015611734573d6000803e3d6000fd5b505050506040513d602081101561174a57600080fd5b5051905060005b835181101561180557600084828151811061176857fe5b602002602001015190506000866001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156117c157600080fd5b505afa1580156117d5573d6000803e3d6000fd5b505050506040513d60208110156117eb57600080fd5b505190506117f9848261577a565b93505050600101611751565b5090505b92915050565b60606000600290506060818451026001600160401b038111801561183257600080fd5b5060405190808252806020026020018201604052801561185c578160200160208202803683370190505b50905060005b8451811015611a0b57600085828151811061187957fe5b6020026020010151905060006001600160a01b0316816001600160a01b031614156118e857866001600160a01b03163183858402815181106118b757fe5b602002602001018181525050600083858402600101815181106118d657fe5b60200260200101818152505050611a03565b806001600160a01b03166370a08231886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561193557600080fd5b505afa158015611949573d6000803e3d6000fd5b505050506040513d602081101561195f57600080fd5b50518351849084870290811061197157fe5b602002602001018181525050806001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156119b657600080fd5b505afa1580156119ca573d6000803e3d6000fd5b505050506040513d60208110156119e057600080fd5b5051835184906001858802019081106119f557fe5b602002602001018181525050505b600101611862565b50949350505050565b60606000600690506060818451026001600160401b0381118015611a3757600080fd5b50604051908082528060200260200182016040528015611a61578160200160208202803683370190505b50905060005b8451811015611a0b576000858281518110611a7e57fe5b602090810291909101810151604080516317e1d38560e11b81526001600160a01b0380841660048301526001602483018190526044830152600060648301529151929450908a1692632fc3a70a92608480840193829003018186803b158015611ae657600080fd5b505afa158015611afa573d6000803e3d6000fd5b505050506040513d6020811015611b1057600080fd5b505183518490848702908110611b2257fe5b602090810291909101810191909152604080516317e1d38560e11b81526001600160a01b0384811660048301526000602483018190526001604484015260648301529151918a1692632fc3a70a92608480840193829003018186803b158015611b8a57600080fd5b505afa158015611b9e573d6000803e3d6000fd5b505050506040513d6020811015611bb457600080fd5b505183518490600185880201908110611bc957fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918a16926356c8c2c192604480840193829003018186803b158015611c2357600080fd5b505afa158015611c37573d6000803e3d6000fd5b505050506040513d6020811015611c4d57600080fd5b505183518490600285880201908110611c6257fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918a16926356c8c2c192604480840193829003018186803b158015611cbc57600080fd5b505afa158015611cd0573d6000803e3d6000fd5b505050506040513d6020811015611ce657600080fd5b505183518490600385880201908110611cfb57fe5b602002602001018181525050866001600160a01b0316636ce8a44b826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611d5457600080fd5b505afa158015611d68573d6000803e3d6000fd5b505050506040513d6020811015611d7e57600080fd5b5051611d8b576000611d8e565b60015b60ff168385840260040181518110611da257fe5b602002602001018181525050866001600160a01b03166348cac277826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611dfb57600080fd5b505afa158015611e0f573d6000803e3d6000fd5b505050506040513d6020811015611e2557600080fd5b505183518490600585880201908110611e3a57fe5b602090810291909101015250600101611a67565b600080600080876001600160a01b03166381a612d6886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611ea157600080fd5b505afa158015611eb5573d6000803e3d6000fd5b505050506040513d6020811015611ecb57600080fd5b5051604080516323b95ceb60e21b81526001600160a01b038a811660048301529151929350600092918b1691638ee573ac91602480820192602092909190829003018186803b158015611f1d57600080fd5b505afa158015611f31573d6000803e3d6000fd5b505050506040513d6020811015611f4757600080fd5b505190506000611f6d68327cb2734119d3b7a9601e1b611f6789866157bc565b90615815565b9050611f89600a83900a611f6783670de0b6b3a76400006157bc565b905060008a6001600160a01b03166342b60b038b6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611fda57600080fd5b505afa158015611fee573d6000803e3d6000fd5b505050506040513d602081101561200457600080fd5b5051801561208757508a6001600160a01b03166342b60b038a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561205a57600080fd5b505afa15801561206e573d6000803e3d6000fd5b505050506040513d602081101561208457600080fd5b50515b90506000816120fa578b6001600160a01b031663a22f23926040518163ffffffff1660e01b815260040160206040518083038186803b1580156120c957600080fd5b505afa1580156120dd573d6000803e3d6000fd5b505050506040513d60208110156120f357600080fd5b5051612160565b8b6001600160a01b031663df73a2676040518163ffffffff1660e01b815260040160206040518083038186803b15801561213357600080fd5b505afa158015612147573d6000803e3d6000fd5b505050506040513d602081101561215d57600080fd5b50515b90506000826121d3578c6001600160a01b0316637a210a2b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156121a257600080fd5b505afa1580156121b6573d6000803e3d6000fd5b505050506040513d60208110156121cc57600080fd5b5051612239565b8c6001600160a01b03166310eb56c26040518163ffffffff1660e01b815260040160206040518083038186803b15801561220c57600080fd5b505afa158015612220573d6000803e3d6000fd5b505050506040513d602081101561223657600080fd5b50515b905060008d6001600160a01b031663c7e074c38e87868660016040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b1580156122ad57600080fd5b505afa1580156122c1573d6000803e3d6000fd5b505050506040513d60208110156122d757600080fd5b8101908080519060200190929190505050905060008e6001600160a01b031663c7e074c38e88878760006040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b15801561235c57600080fd5b505afa158015612370573d6000803e3d6000fd5b505050506040513d602081101561238657600080fd5b505190506000818311612399578161239b565b825b9b509199509750505050505050509450945094915050565b60606000600790506060818451026001600160401b03811180156123d657600080fd5b50604051908082528060200260200182016040528015612400578160200160208202803683370190505b50905060005b8451811015611a0b57600085828151811061241d57fe5b60200260200101519050806001600160a01b0316635d50e729886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561247457600080fd5b505afa158015612488573d6000803e3d6000fd5b505050506040513d602081101561249e57600080fd5b5051835184908487029081106124b057fe5b602002602001018181525050806001600160a01b031663d5a73fdd886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561250957600080fd5b505afa15801561251d573d6000803e3d6000fd5b505050506040513d602081101561253357600080fd5b50518351849060018588020190811061254857fe5b60200260200101818152505085828151811061256057fe5b60200260200101516001600160a01b03166370a08231886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156125b457600080fd5b505afa1580156125c8573d6000803e3d6000fd5b505050506040513d60208110156125de57600080fd5b5051835184906002858802019081106125f357fe5b602002602001018181525050806001600160a01b03166371417b32886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561264c57600080fd5b505afa158015612660573d6000803e3d6000fd5b505050506040513d602081101561267657600080fd5b50518351849060038588020190811061268b57fe5b602002602001018181525050806001600160a01b031663402914f5886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156126e457600080fd5b505afa1580156126f8573d6000803e3d6000fd5b505050506040513d602081101561270e57600080fd5b50518351849060048588020190811061272357fe5b602002602001018181525050806001600160a01b03166308f26c76886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561277c57600080fd5b505afa158015612790573d6000803e3d6000fd5b505050506040513d60208110156127a657600080fd5b5051835184906005858802019081106127bb57fe5b602002602001018181525050806001600160a01b03166345f01ee6886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561281457600080fd5b505afa158015612828573d6000803e3d6000fd5b505050506040513d602081101561283e57600080fd5b50518351849060068588020190811061285357fe5b602090810291909101015250600101612406565b60606000600c905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b1580156128af57600080fd5b505afa1580156128c3573d6000803e3d6000fd5b505050506040513d60208110156128d957600080fd5b5051855190915060609084026001600160401b03811180156128fa57600080fd5b50604051908082528060200260200182016040528015612924578160200160208202803683370190505b50905060005b86518110156115de57600087828151811061294157fe5b6020026020010151905060006001600160a01b0316816001600160a01b031614156129695750885b846001600160a01b03166352f55eed826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156129b657600080fd5b505afa1580156129ca573d6000803e3d6000fd5b505050506040513d60208110156129e057600080fd5b5051835184908489029081106129f257fe5b602002602001018181525050846001600160a01b031663c3c7b9e9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612a4b57600080fd5b505afa158015612a5f573d6000803e3d6000fd5b505050506040513d6020811015612a7557600080fd5b5051835184906001858a0201908110612a8a57fe5b602002602001018181525050846001600160a01b0316631aa4ace5826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612ae357600080fd5b505afa158015612af7573d6000803e3d6000fd5b505050506040513d6020811015612b0d57600080fd5b5051835184906002858a0201908110612b2257fe5b602002602001018181525050846001600160a01b0316632c668ec1828b6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b158015612b8357600080fd5b505afa158015612b97573d6000803e3d6000fd5b505050506040513d6020811015612bad57600080fd5b5051835184906003858a0201908110612bc257fe5b602002602001018181525050846001600160a01b031663ab2f3ad4826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612c1b57600080fd5b505afa158015612c2f573d6000803e3d6000fd5b505050506040513d6020811015612c4557600080fd5b5051835184906004858a0201908110612c5a57fe5b602002602001018181525050846001600160a01b0316634a993ee9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612cb357600080fd5b505afa158015612cc7573d6000803e3d6000fd5b505050506040513d6020811015612cdd57600080fd5b5051835184906005858a0201908110612cf257fe5b602002602001018181525050846001600160a01b031663ad1e4f8d826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612d4b57600080fd5b505afa158015612d5f573d6000803e3d6000fd5b505050506040513d6020811015612d7557600080fd5b5051835184906006858a0201908110612d8a57fe5b602002602001018181525050846001600160a01b03166381a612d6826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612de357600080fd5b505afa158015612df7573d6000803e3d6000fd5b505050506040513d6020811015612e0d57600080fd5b5051835184906007858a0201908110612e2257fe5b602002602001018181525050846001600160a01b031663e124e6d2826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612e7b57600080fd5b505afa158015612e8f573d6000803e3d6000fd5b505050506040513d6020811015612ea557600080fd5b5051835184906008858a0201908110612eba57fe5b602002602001018181525050846001600160a01b031663f07456ce826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612f1357600080fd5b505afa158015612f27573d6000803e3d6000fd5b505050506040513d6020811015612f3d57600080fd5b5051835184906009858a0201908110612f5257fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918716926356c8c2c192604480840193829003018186803b158015612fac57600080fd5b505afa158015612fc0573d6000803e3d6000fd5b505050506040513d6020811015612fd657600080fd5b505183518490600a858a0201908110612feb57fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918716926356c8c2c192604480840193829003018186803b15801561304557600080fd5b505afa158015613059573d6000803e3d6000fd5b505050506040513d602081101561306f57600080fd5b505183518490600b858a020190811061308457fe5b60209081029190910101525060010161292a565b60608082516001600160401b03811180156130b257600080fd5b506040519080825280602002602001820160405280156130dc578160200160208202803683370190505b50905060005b835181101561180557846001600160a01b0316631ce9cb8f85838151811061310657fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561314b57600080fd5b505afa15801561315f573d6000803e3d6000fd5b505050506040513d602081101561317557600080fd5b5051825183908390811061318557fe5b60209081029190910101526001016130e2565b601281565b60606000600e905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b1580156131e557600080fd5b505afa1580156131f9573d6000803e3d6000fd5b505050506040513d602081101561320f57600080fd5b5051855190915060609084026001600160401b038111801561323057600080fd5b5060405190808252806020026020018201604052801561325a578160200160208202803683370190505b50905060005b86518110156115de57600087828151811061327757fe5b6020026020010151905060006001600160a01b0316816001600160a01b0316141561329f5750885b60008054600160a01b900460ff166132b8576000613332565b856001600160a01b0316639698d25a836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561330557600080fd5b505afa158015613319573d6000803e3d6000fd5b505050506040513d602081101561332f57600080fd5b50515b9050856001600160a01b03166352f55eed836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561338157600080fd5b505afa158015613395573d6000803e3d6000fd5b505050506040513d60208110156133ab57600080fd5b505184518590858a029081106133bd57fe5b602002602001018181525050856001600160a01b031663c3c7b9e9836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561341657600080fd5b505afa15801561342a573d6000803e3d6000fd5b505050506040513d602081101561344057600080fd5b5051845185906001868b020190811061345557fe5b602002602001018181525050856001600160a01b0316631aa4ace5836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156134ae57600080fd5b505afa1580156134c2573d6000803e3d6000fd5b505050506040513d60208110156134d857600080fd5b5051845185906002868b02019081106134ed57fe5b602002602001018181525050856001600160a01b0316632c668ec1838c6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b15801561354e57600080fd5b505afa158015613562573d6000803e3d6000fd5b505050506040513d602081101561357857600080fd5b5051845185906003868b020190811061358d57fe5b602002602001018181525050856001600160a01b031663ab2f3ad4836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156135e657600080fd5b505afa1580156135fa573d6000803e3d6000fd5b505050506040513d602081101561361057600080fd5b5051845185906004868b020190811061362557fe5b602002602001018181525050856001600160a01b0316634a993ee9836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561367e57600080fd5b505afa158015613692573d6000803e3d6000fd5b505050506040513d60208110156136a857600080fd5b5051845185906005868b02019081106136bd57fe5b602002602001018181525050856001600160a01b031663ad1e4f8d836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561371657600080fd5b505afa15801561372a573d6000803e3d6000fd5b505050506040513d602081101561374057600080fd5b5051845185906006868b020190811061375557fe5b602002602001018181525050856001600160a01b0316638a78daa8836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156137ae57600080fd5b505afa1580156137c2573d6000803e3d6000fd5b505050506040513d60208110156137d857600080fd5b5051845185906007868b02019081106137ed57fe5b60200260200101818152505080848885026008018151811061380b57fe5b602002602001018181525050856001600160a01b03166381a612d6836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561386457600080fd5b505afa158015613878573d6000803e3d6000fd5b505050506040513d602081101561388e57600080fd5b5051845185906009868b02019081106138a357fe5b602002602001018181525050856001600160a01b031663e124e6d2836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156138fc57600080fd5b505afa158015613910573d6000803e3d6000fd5b505050506040513d602081101561392657600080fd5b505184518590600a868b020190811061393b57fe5b602002602001018181525050856001600160a01b031663f07456ce836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561399457600080fd5b505afa1580156139a8573d6000803e3d6000fd5b505050506040513d60208110156139be57600080fd5b505184518590600b868b02019081106139d357fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038581166004830152600060248301529151918816926356c8c2c192604480840193829003018186803b158015613a2d57600080fd5b505afa158015613a41573d6000803e3d6000fd5b505050506040513d6020811015613a5757600080fd5b505184518590600c868b0201908110613a6c57fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038581166004830152600160248301529151918816926356c8c2c192604480840193829003018186803b158015613ac657600080fd5b505afa158015613ada573d6000803e3d6000fd5b505050506040513d6020811015613af057600080fd5b505184518590600d868b0201908110613b0557fe5b60209081029190910101525050600101613260565b60606000600290506060818451026001600160401b0381118015613b3d57600080fd5b50604051908082528060200260200182016040528015613b67578160200160208202803683370190505b50905060005b8451811015611a0b576000858281518110613b8457fe5b60200260200101519050806001600160a01b031663402914f5886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613bdb57600080fd5b505afa158015613bef573d6000803e3d6000fd5b505050506040513d6020811015613c0557600080fd5b505183518490848702908110613c1757fe5b602002602001018181525050806001600160a01b0316632459f51d6040518163ffffffff1660e01b815260040160206040518083038186803b158015613c5c57600080fd5b505afa158015613c70573d6000803e3d6000fd5b505050506040513d6020811015613c8657600080fd5b505183518490600185880201908110613c9b57fe5b602090810291909101015250600101613b6d565b68327cb2734119d3b7a9601e1b81565b60606000600290506060818451026001600160401b0381118015613ce257600080fd5b50604051908082528060200260200182016040528015613d0c578160200160208202803683370190505b5090508560005b855181101561417f576000868281518110613d2a57fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415613d525750865b6000836001600160a01b03166342b60b03836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613da157600080fd5b505afa158015613db5573d6000803e3d6000fd5b505050506040513d6020811015613dcb57600080fd5b5051613e3b57836001600160a01b031663c4f718bf6040518163ffffffff1660e01b815260040160206040518083038186803b158015613e0a57600080fd5b505afa158015613e1e573d6000803e3d6000fd5b505050506040513d6020811015613e3457600080fd5b5051613ea1565b836001600160a01b031663134ca63b6040518163ffffffff1660e01b815260040160206040518083038186803b158015613e7457600080fd5b505afa158015613e88573d6000803e3d6000fd5b505050506040513d6020811015613e9e57600080fd5b50515b90506000846001600160a01b031663c3c7b9e9846040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613ef257600080fd5b505afa158015613f06573d6000803e3d6000fd5b505050506040513d6020811015613f1c57600080fd5b5051604080516352f55eed60e01b81526001600160a01b0386811660048301529151929350600092918816916352f55eed91602480820192602092909190829003018186803b158015613f6e57600080fd5b505afa158015613f82573d6000803e3d6000fd5b505050506040513d6020811015613f9857600080fd5b505190508015613fcb57613fb081611f6785856157bc565b8789870281518110613fbe57fe5b6020026020010181815250505b6000866001600160a01b031663c65bc7b1866040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561401a57600080fd5b505afa15801561402e573d6000803e3d6000fd5b505050506040513d602081101561404457600080fd5b5051111561416f576000866001600160a01b031663a93acac2866040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561409b57600080fd5b505afa1580156140af573d6000803e3d6000fd5b505050506040513d60208110156140c557600080fd5b50516040805163c65bc7b160e01b81526001600160a01b0388811660048301529151929350600092918a169163c65bc7b191602480820192602092909190829003018186803b15801561411757600080fd5b505afa15801561412b573d6000803e3d6000fd5b505050506040513d602081101561414157600080fd5b5051905061414f8183615854565b898b89026001018151811061416057fe5b60200260200101818152505050505b505060019092019150613d139050565b5090925050505b9392505050565b6000546001600160a01b031633146141e4576040805162461bcd60e51b815260206004820152601560248201527423b7bb32b93730b136329d103337b93134b23232b760591b604482015290519081900360640190fd5b60008054911515600160a01b0260ff60a01b19909216919091179055565b80516060906002908190839082900482026001600160401b038111801561422857600080fd5b50604051908082528060200260200182016040528015614252578160200160208202803683370190505b50905060005b8386518161426257fe5b04811015614464576000868583028151811061427a57fe5b602002602001015190506000878684026001018151811061429757fe5b602002602001015190506000896001600160a01b031663e6a4390584846040518363ffffffff1660e01b815260040180836001600160a01b03168152602001826001600160a01b031681526020019250505060206040518083038186803b15801561430157600080fd5b505afa158015614315573d6000803e3d6000fd5b505050506040513d602081101561432b57600080fd5b5051604080516370a0823160e01b81526001600160a01b0380841660048301529151929350908516916370a0823191602480820192602092909190829003018186803b15801561437a57600080fd5b505afa15801561438e573d6000803e3d6000fd5b505050506040513d60208110156143a457600080fd5b5051855186908689029081106143b657fe5b602002602001018181525050816001600160a01b03166370a08231826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561440f57600080fd5b505afa158015614423573d6000803e3d6000fd5b505050506040513d602081101561443957600080fd5b5051855186906001878a020190811061444e57fe5b6020908102919091010152505050600101614258565b5095945050505050565b600981565b600054600160a01b900460ff1681565b6000546001600160a01b031633146144da576040805162461bcd60e51b815260206004820152601560248201527423b7bb32b93730b136329d103337b93134b23232b760591b604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000806000866001600160a01b03166381a612d6876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561454e57600080fd5b505afa158015614562573d6000803e3d6000fd5b505050506040513d602081101561457857600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0389811660048301529151929350600092918a1691638ee573ac91602480820192602092909190829003018186803b1580156145ca57600080fd5b505afa1580156145de573d6000803e3d6000fd5b505050506040513d60208110156145f457600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0389811660048301529151929350600092918b1691638ee573ac91602480820192602092909190829003018186803b15801561464657600080fd5b505afa15801561465a573d6000803e3d6000fd5b505050506040513d602081101561467057600080fd5b5051905060008061469168327cb2734119d3b7a9601e1b611f678a886157bc565b90506146ad600a85900a611f6783670de0b6b3a76400006157bc565b905060008b6001600160a01b03166342b60b038c6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156146fe57600080fd5b505afa158015614712573d6000803e3d6000fd5b505050506040513d602081101561472857600080fd5b505180156147ab57508b6001600160a01b03166342b60b038b6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561477e57600080fd5b505afa158015614792573d6000803e3d6000fd5b505050506040513d60208110156147a857600080fd5b50515b905060008161481e578c6001600160a01b031663a22f23926040518163ffffffff1660e01b815260040160206040518083038186803b1580156147ed57600080fd5b505afa158015614801573d6000803e3d6000fd5b505050506040513d602081101561481757600080fd5b5051614884565b8c6001600160a01b031663df73a2676040518163ffffffff1660e01b815260040160206040518083038186803b15801561485757600080fd5b505afa15801561486b573d6000803e3d6000fd5b505050506040513d602081101561488157600080fd5b50515b90506000826148f7578d6001600160a01b0316637a210a2b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156148c657600080fd5b505afa1580156148da573d6000803e3d6000fd5b505050506040513d60208110156148f057600080fd5b505161495d565b8d6001600160a01b03166310eb56c26040518163ffffffff1660e01b815260040160206040518083038186803b15801561493057600080fd5b505afa158015614944573d6000803e3d6000fd5b505050506040513d602081101561495a57600080fd5b50515b905060008e6001600160a01b031663c7e074c38f87868660016040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b1580156149d157600080fd5b505afa1580156149e5573d6000803e3d6000fd5b505050506040513d60208110156149fb57600080fd5b8101908080519060200190929190505050905060008f6001600160a01b031663c7e074c38f88878760006040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b158015614a8057600080fd5b505afa158015614a94573d6000803e3d6000fd5b505050506040513d6020811015614aaa57600080fd5b50519050808211614abb5780614abd565b815b965050505050505060008a6001600160a01b031663e124e6d28a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015614b1457600080fd5b505afa158015614b28573d6000803e3d6000fd5b505050506040513d6020811015614b3e57600080fd5b505190506000614b5282611f678b896157bc565b9050614b7185600a0a611f6786600a0a846157bc90919063ffffffff16565b90506000614b8f612710611f67614b88828861577a565b85906157bc565b90506000614b9d838361577a565b919e919d50909b505050505050505050505050565b60608082516001600160401b0381118015614bcc57600080fd5b50604051908082528060200260200182016040528015614bf6578160200160208202803683370190505b50905060005b8351811015611805576000848281518110614c1357fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415614c6157856001600160a01b031631838381518110614c4f57fe5b60200260200101818152505050614cf6565b806001600160a01b03166370a08231876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015614cae57600080fd5b505afa158015614cc2573d6000803e3d6000fd5b505050506040513d6020811015614cd857600080fd5b50518351849084908110614ce857fe5b602002602001018181525050505b600101614bfc565b60608060098551026001600160401b0381118015614d1b57600080fd5b50604051908082528060200260200182016040528015614d45578160200160208202803683370190505b50905060005b85518110156150ef5760008060008060008060008e6001600160a01b0316634a3f088d8f8f8b81518110614d7b57fe5b60200260200101518f8c81518110614d8f57fe5b60200260200101518f8d81518110614da357fe5b60200260200101516040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b0316815260200182151581526020019450505050506101006040518083038186803b158015614e1257600080fd5b505afa158015614e26573d6000803e3d6000fd5b505050506040513d610100811015614e3d57600080fd5b50805160208201516040830151606084015160a085015160c086015160e0909601518f51959d50939b509199509750955091935090915087908a9060098b02908110614e8557fe5b602002602001018181525050858960098a0260010181518110614ea457fe5b602002602001018181525050848960098a0260020181518110614ec357fe5b602002602001018181525050838960098a0260030181518110614ee257fe5b60200260200101818152505081614efa576000614efd565b60015b60ff168960098a0260040181518110614f1257fe5b602002602001018181525050828960098a0260050181518110614f3157fe5b602002602001018181525050808960098a0260060181518110614f5057fe5b602002602001018181525050505050505050506000826009830281518110614f7457fe5b602002602001015190506000836009840260020181518110614f9257fe5b602002602001015190506000846009850260060181518110614fb057fe5b6020026020010151905060008211156150e4576000808c6001600160a01b0316635c07eaab8b8881518110614fe157fe5b602002602001015187878d8b81518110614ff757fe5b6020026020010151886040518663ffffffff1660e01b815260040180866001600160a01b03168152602001858152602001848152602001831515815260200182815260200195505050505050604080518083038186803b15801561505a57600080fd5b505afa15801561506e573d6000803e3d6000fd5b505050506040513d604081101561508457600080fd5b50805160209091015190925090508161509e5760006150a1565b60015b60ff168760098802600701815181106150b657fe5b602002602001018181525050808760098802600801815181106150d557fe5b60200260200101818152505050505b505050600101614d4b565b509695505050505050565b600080846001600160a01b03166381a612d6856040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561514a57600080fd5b505afa15801561515e573d6000803e3d6000fd5b505050506040513d602081101561517457600080fd5b505160408051637092736960e11b81526001600160a01b03868116600483015291519293506000929188169163e124e6d291602480820192602092909190829003018186803b1580156151c657600080fd5b505afa1580156151da573d6000803e3d6000fd5b505050506040513d60208110156151f057600080fd5b5051604080516323b95ceb60e21b81526001600160a01b038881166004830152915192935060009291891691638ee573ac91602480820192602092909190829003018186803b15801561524257600080fd5b505afa158015615256573d6000803e3d6000fd5b505050506040513d602081101561526c57600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0388811660048301529151929350600092918a1691638ee573ac91602480820192602092909190829003018186803b1580156152be57600080fd5b505afa1580156152d2573d6000803e3d6000fd5b505050506040513d60208110156152e857600080fd5b5051604080516352f55eed60e01b81526001600160a01b038981166004830152915192935060009283928c16916352f55eed916024808301926020929190829003018186803b15801561533a57600080fd5b505afa15801561534e573d6000803e3d6000fd5b505050506040513d602081101561536457600080fd5b50516040805163c3c7b9e960e01b81526001600160a01b038b811660048301529151929350600092918d169163c3c7b9e991602480820192602092909190829003018186803b1580156153b657600080fd5b505afa1580156153ca573d6000803e3d6000fd5b505050506040513d60208110156153e057600080fd5b505160408051634a993ee960e01b81526001600160a01b038c811660048301529151929350600092918e1691634a993ee991602480820192602092909190829003018186803b15801561543257600080fd5b505afa158015615446573d6000803e3d6000fd5b505050506040513d602081101561545c57600080fd5b50519050600081831161546f5781615471565b825b905083811061548c5760009950505050505050505050614186565b6000615498858361577a565b90506154c587600a0a611f678a600a0a6154bf8e611f678f886157bc90919063ffffffff16565b906157bc565b955050505050506000896001600160a01b031663ad1e4f8d8a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561551b57600080fd5b505afa15801561552f573d6000803e3d6000fd5b505050506040513d602081101561554557600080fd5b5051905080156115de57896001600160a01b0316631aa4ace58a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561559c57600080fd5b505afa1580156155b0573d6000803e3d6000fd5b505050506040513d60208110156155c657600080fd5b50518110156155de5760009650505050505050614186565b60006156638b6001600160a01b0316631aa4ace58c6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561563057600080fd5b505afa158015615644573d6000803e3d6000fd5b505050506040513d602081101561565a57600080fd5b5051839061577a565b905061567f670de0b6b3a7640000611f6783600a89900a6157bc565b905061569b87611f678368327cb2734119d3b7a9601e1b6157bc565b9050808311156156b357965061418695505050505050565b505098975050505050505050565b600080805b83518110156118055760008482815181106156dd57fe5b602002602001015190506000866001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561573657600080fd5b505afa15801561574a573d6000803e3d6000fd5b505050506040513d602081101561576057600080fd5b5051905061576e8482615854565b935050506001016156c6565b600061418683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506158ac565b6000826157cb57506000611809565b828202828482816157d857fe5b04146141865760405162461bcd60e51b81526004018080602001828103825260218152602001806159a96021913960400191505060405180910390fd5b600061418683836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250615943565b600082820183811015614186576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6000818484111561593b5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156159005781810151838201526020016158e8565b50505050905090810190601f16801561592d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600081836159925760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156159005781810151838201526020016158e8565b50600083858161599e57fe5b049594505050505056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a264697066735822122045b628c599a8154fc42d4309c34d7a311429d723c48990eab8da614a960534d364736f6c634300060c0033 \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/Router.abi b/src/Managing.Tools.ABI/Gmx/core/Router.abi new file mode 100644 index 0000000..16f6965 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/Router.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_usdg","type":"address"},{"internalType":"address","name":"_weth","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":false,"internalType":"address","name":"tokenOut","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"Swap","type":"event"},{"inputs":[{"internalType":"address","name":"_plugin","type":"address"}],"name":"addPlugin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_plugin","type":"address"}],"name":"approvePlugin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"approvedPlugins","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"decreasePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"}],"name":"decreasePositionAndSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address payable","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"}],"name":"decreasePositionAndSwapETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address payable","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"decreasePositionETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_plugin","type":"address"}],"name":"denyPlugin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"directPoolDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gov","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"increasePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"increasePositionETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_collateralDelta","type":"uint256"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"pluginDecreasePosition","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"address","name":"_indexToken","type":"address"},{"internalType":"uint256","name":"_sizeDelta","type":"uint256"},{"internalType":"bool","name":"_isLong","type":"bool"}],"name":"pluginIncreasePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_account","type":"address"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"pluginTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"plugins","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_plugin","type":"address"}],"name":"removePlugin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gov","type":"address"}],"name":"setGov","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"swapETHToTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minOut","type":"uint256"},{"internalType":"address payable","name":"_receiver","type":"address"}],"name":"swapTokensToETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"usdg","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Gmx/core/Router.bin b/src/Managing.Tools.ABI/Gmx/core/Router.bin new file mode 100644 index 0000000..f55e113 --- /dev/null +++ b/src/Managing.Tools.ABI/Gmx/core/Router.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516124773803806124778339818101604052606081101561003357600080fd5b5080516020820151604090920151600380546001600160a01b039384166001600160a01b0319918216179091556002805494841694821694909417909355600180549290911691831691909117905560008054909116331790556123db8061009c6000396000f3fe60806040526004361061011f5760003560e01c806312d43a51146101835780631b827878146101b45780631f1dd176146101fd5780632662166b1461024e5780632d4ba6a7146102c15780633039e37f1461038357806338c74dd9146104635780633fc8cef314610496578063430ed37c146104ab5780634b12e6431461050a5780635fc8500e146105515780636023e9661461063157806390205d8c146106f357806390b64ad314610752578063956f285e1461078b578063a4d95b64146107c6578063abe68eaa146107f9578063b32755de146108a8578063b7ddc9921461096b578063cedd437514610a41578063cfad57a214610a74578063d8867fc814610aa7578063f5b91b7b14610ada578063fbfa77cf14610aef5761017e565b3661017e576001546001600160a01b0316331461017c576040805162461bcd60e51b81526020600482015260166024820152752937baba32b91d1034b73b30b634b21039b2b73232b960511b604482015290519081900360640190fd5b005b600080fd5b34801561018f57600080fd5b50610198610b04565b604080516001600160a01b039092168252519081900360200190f35b3480156101c057600080fd5b5061017c600480360360808110156101d757600080fd5b506001600160a01b03813581169160208101358216916040820135169060600135610b13565b34801561020957600080fd5b5061017c600480360360a081101561022057600080fd5b506001600160a01b038135811691602081013582169160408201351690606081013590608001351515610b37565b34801561025a57600080fd5b506102af600480360360e081101561027157600080fd5b506001600160a01b0381358116916020810135821691604082013581169160608101359160808201359160a081013515159160c09091013516610bcc565b60408051918252519081900360200190f35b3480156102cd57600080fd5b5061017c600480360360808110156102e457600080fd5b810190602081018135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460208302840111600160201b8311171561033157600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604001356001600160a01b0316610c8c565b34801561038f57600080fd5b5061017c60048036036101008110156103a757600080fd5b810190602081018135600160201b8111156103c157600080fd5b8201836020820111156103d357600080fd5b803590602001918460208302840111600160201b831117156103f457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b0390811693506020830135926040810135925060608101351515916080820135169060a08101359060c00135610df3565b34801561046f57600080fd5b5061017c6004803603602081101561048657600080fd5b50356001600160a01b0316610ef8565b3480156104a257600080fd5b50610198610f29565b3480156104b757600080fd5b5061017c600480360360e08110156104ce57600080fd5b506001600160a01b0381358116916020810135821691604082013591606081013591608082013515159160a08101359091169060c00135610f38565b34801561051657600080fd5b5061053d6004803603602081101561052d57600080fd5b50356001600160a01b0316610f5f565b604080519115158252519081900360200190f35b34801561055d57600080fd5b5061017c600480360361010081101561057557600080fd5b810190602081018135600160201b81111561058f57600080fd5b8201836020820111156105a157600080fd5b803590602001918460208302840111600160201b831117156105c257600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b0390811693506020830135926040810135925060608101351515916080820135169060a08101359060c00135610f74565b34801561063d57600080fd5b5061017c6004803603608081101561065457600080fd5b810190602081018135600160201b81111561066e57600080fd5b82018360208201111561068057600080fd5b803590602001918460208302840111600160201b831117156106a157600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604001356001600160a01b0316610fba565b3480156106ff57600080fd5b5061017c600480360360e081101561071657600080fd5b506001600160a01b0381358116916020810135821691604082013591606081013591608082013515159160a08101359091169060c00135610ff1565b34801561075e57600080fd5b5061017c6004803603604081101561077557600080fd5b506001600160a01b038135169060200135611000565b34801561079757600080fd5b5061053d600480360360408110156107ae57600080fd5b506001600160a01b038135811691602001351661108c565b3480156107d257600080fd5b5061017c600480360360208110156107e957600080fd5b50356001600160a01b03166110ac565b61017c6004803603606081101561080f57600080fd5b810190602081018135600160201b81111561082957600080fd5b82018360208201111561083b57600080fd5b803590602001918460208302840111600160201b8311171561085c57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602001356001600160a01b0316611120565b61017c600480360360c08110156108be57600080fd5b810190602081018135600160201b8111156108d857600080fd5b8201836020820111156108ea57600080fd5b803590602001918460208302840111600160201b8311171561090b57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383351693505050602081013590604081013590606081013515159060800135611235565b34801561097757600080fd5b5061017c600480360360e081101561098e57600080fd5b810190602081018135600160201b8111156109a857600080fd5b8201836020820111156109ba57600080fd5b803590602001918460208302840111600160201b831117156109db57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383351693505050602081013590604081013590606081013590608081013515159060a00135611325565b348015610a4d57600080fd5b5061017c60048036036020811015610a6457600080fd5b50356001600160a01b03166113d2565b348015610a8057600080fd5b5061017c60048036036020811015610a9757600080fd5b50356001600160a01b0316611400565b348015610ab357600080fd5b5061017c60048036036020811015610aca57600080fd5b50356001600160a01b0316611475565b348015610ae657600080fd5b506101986114ec565b348015610afb57600080fd5b506101986114fb565b6000546001600160a01b031681565b610b1c8361150a565b610b316001600160a01b0385168484846115e0565b50505050565b610b408561150a565b600354604080516348d91abf60e01b81526001600160a01b03888116600483015287811660248301528681166044830152606482018690528415156084830152915191909216916348d91abf9160a480830192600092919082900301818387803b158015610bad57600080fd5b505af1158015610bc1573d6000803e3d6000fd5b505050505050505050565b6000610bd78861150a565b6003546040805163082a084960e41b81526001600160a01b038b811660048301528a811660248301528981166044830152606482018990526084820188905286151560a483015285811660c4830152915191909216916382a084909160e48083019260209291908290030181600087803b158015610c5457600080fd5b505af1158015610c68573d6000803e3d6000fd5b505050506040513d6020811015610c7e57600080fd5b505198975050505050505050565b60015484516001600160a01b039091169085906000198101908110610cad57fe5b60200260200101516001600160a01b031614610cfe576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b610d48610d0961163a565b60035486516001600160a01b039091169086908890600090610d2757fe5b60200260200101516001600160a01b03166115e0909392919063ffffffff16565b6000610d5585843061163e565b9050610d61818361177e565b6000805160206123388339815191523386600081518110610d7e57fe5b602002602001015187600189510381518110610d9657fe5b6020026020010151878560405180866001600160a01b03168152602001856001600160a01b03168152602001846001600160a01b031681526020018381526020018281526020019550505050505060405180910390a15050505050565b60015488516001600160a01b039091169089906000198101908110610e1457fe5b60200260200101516001600160a01b031614610e65576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b6000610e8a89600081518110610e7757fe5b60200260200101518989898930896117fb565b9050610ed3600360009054906101000a90046001600160a01b0316828b600081518110610eb357fe5b60200260200101516001600160a01b0316611a089092919063ffffffff16565b6000610ee08a843061163e565b9050610eec818661177e565b50505050505050505050565b3360009081526005602090815260408083206001600160a01b0394909416835292905220805460ff19166001179055565b6001546001600160a01b031681565b6000610f49888888888830886117fb565b9050610f55818461177e565b5050505050505050565b60046020526000908152604090205460ff1681565b6000610f8689600081518110610e7757fe5b9050610faf600360009054906101000a90046001600160a01b0316828b600081518110610eb357fe5b610eec89838661163e565b610fc5610d0961163a565b6000610fd285848461163e565b90506000805160206123388339815191523386600081518110610d7e57fe5b610f55878787878787876117fb565b61102261100b61163a565b6003546001600160a01b03858116929116846115e0565b60035460408051635f7bc11960e01b81526001600160a01b03858116600483015291519190921691635f7bc11991602480830192600092919082900301818387803b15801561107057600080fd5b505af1158015611084573d6000803e3d6000fd5b505050505050565b600560209081526000928352604080842090915290825290205460ff1681565b6000546001600160a01b031633146110ff576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b6001600160a01b03166000908152600460205260409020805460ff19169055565b60015483516001600160a01b0390911690849060009061113c57fe5b60200260200101516001600160a01b03161461118d576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b611195611a5f565b60006111a284848461163e565b905060008051602061233883398151915233856000815181106111c157fe5b6020026020010151866001885103815181106111d957fe5b6020026020010151348560405180866001600160a01b03168152602001856001600160a01b03168152602001846001600160a01b031681526020018381526020018281526020019550505050505060405180910390a150505050565b60015486516001600160a01b0390911690879060009061125157fe5b60200260200101516001600160a01b0316146112a2576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b34156112b0576112b0611a5f565b600186511180156112c15750600034115b156113015760006112d387863061163e565b90506112ff600360009054906101000a90046001600160a01b0316828960018b510381518110610eb357fe5b505b6110848660018851038151811061131457fe5b602002602001015186858585611ae8565b84156113545761135461133661163a565b60035489516001600160a01b039091169088908b90600090610d2757fe5b600187511180156113655750600085115b156113a557600061137788863061163e565b90506113a3600360009054906101000a90046001600160a01b0316828a60018c510381518110610eb357fe5b505b6113c9876001895103815181106113b857fe5b602002602001015187858585611ae8565b50505050505050565b3360009081526005602090815260408083206001600160a01b0394909416835292905220805460ff19169055565b6000546001600160a01b03163314611453576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146114c8576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b6001600160a01b03166000908152600460205260409020805460ff19166001179055565b6002546001600160a01b031681565b6003546001600160a01b031681565b3360009081526004602052604090205460ff16611567576040805162461bcd60e51b81526020600482015260166024820152752937baba32b91d1034b73b30b634b21038363ab3b4b760511b604482015290519081900360640190fd5b6001600160a01b038116600090815260056020908152604080832033845290915290205460ff166115dd576040805162461bcd60e51b815260206004820152601b60248201527a149bdd5d195c8e881c1b1d59da5b881b9bdd08185c1c1c9bdd9959602a1b604482015290519081900360640190fd5b50565b604080516001600160a01b0380861660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610b31908590611ce3565b3390565b60008351600214156116855761167e8460008151811061165a57fe5b60200260200101518560018151811061166f57fe5b60200260200101518585611d94565b9050611777565b83516003141561172b5760006116c6856000815181106116a157fe5b6020026020010151866001815181106116b657fe5b6020026020010151600030611d94565b90506116ef600360009054906101000a90046001600160a01b03168287600181518110610eb357fe5b611723856001815181106116ff57fe5b60200260200101518660028151811061171457fe5b60200260200101518686611d94565b915050611777565b6040805162461bcd60e51b815260206004820152601c60248201527b0a4deeae8cae47440d2dcecc2d8d2c840bee0c2e8d05cd8cadccee8d60231b604482015290519081900360640190fd5b9392505050565b60015460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156117cb57600080fd5b505af11580156117df573d6000803e3d6000fd5b506117f7925050506001600160a01b03821683611f92565b5050565b600083156118c157600354604080516340d3096b60e11b81526001600160a01b038a811660048301529151859392909216916381a612d691602480820192602092909190829003018186803b15801561185357600080fd5b505afa158015611867573d6000803e3d6000fd5b505050506040513d602081101561187d57600080fd5b505110156118bc5760405162461bcd60e51b81526004018080602001828103825260238152602001806123156023913960400191505060405180910390fd5b61197a565b60035460408051637092736960e11b81526001600160a01b038a8116600483015291518593929092169163e124e6d291602480820192602092909190829003018186803b15801561191157600080fd5b505afa158015611925573d6000803e3d6000fd5b505050506040513d602081101561193b57600080fd5b5051111561197a5760405162461bcd60e51b81526004018080602001828103825260248152602001806123826024913960400191505060405180910390fd5b6003546001600160a01b03166382a0849061199361163a565b604080516001600160e01b031960e085901b1681526001600160a01b039283166004820152828d166024820152828c166044820152606481018b9052608481018a905288151560a482015291871660c48301525160e48083019260209291908290030181600087803b158015610c5457600080fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052611a5a908490611ce3565b505050565b600160009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015611aaf57600080fd5b505af1158015611ac3573d6000803e3d6000fd5b5050600354600154611ae694506001600160a01b03908116935016905034611a08565b565b8115611bac5760035460408051637092736960e11b81526001600160a01b03878116600483015291518493929092169163e124e6d291602480820192602092909190829003018186803b158015611b3e57600080fd5b505afa158015611b52573d6000803e3d6000fd5b505050506040513d6020811015611b6857600080fd5b50511115611ba75760405162461bcd60e51b81526004018080602001828103825260248152602001806123826024913960400191505060405180910390fd5b611c65565b600354604080516340d3096b60e11b81526001600160a01b0387811660048301529151849392909216916381a612d691602480820192602092909190829003018186803b158015611bfc57600080fd5b505afa158015611c10573d6000803e3d6000fd5b505050506040513d6020811015611c2657600080fd5b50511015611c655760405162461bcd60e51b81526004018080602001828103825260238152602001806123156023913960400191505060405180910390fd5b6003546001600160a01b03166348d91abf611c7e61163a565b604080516001600160e01b031960e085901b1681526001600160a01b039283166004820152828a16602482015291881660448301526064820187905285151560848301525160a480830192600092919082900301818387803b158015610bad57600080fd5b6060611d38826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166120779092919063ffffffff16565b805190915015611a5a57808060200190516020811015611d5757600080fd5b5051611a5a5760405162461bcd60e51b815260040180806020018281038252602a815260200180612358602a913960400191505060405180910390fd5b60025460009081906001600160a01b0386811691161415611e39576003546040805163817bb85760e01b81526001600160a01b03898116600483015286811660248301529151919092169163817bb8579160448083019260209291908290030181600087803b158015611e0657600080fd5b505af1158015611e1a573d6000803e3d6000fd5b505050506040513d6020811015611e3057600080fd5b50519050611f34565b6002546001600160a01b0387811691161415611ea65760035460408051630711e61960e41b81526001600160a01b03888116600483015286811660248301529151919092169163711e61909160448083019260209291908290030181600087803b158015611e0657600080fd5b60035460408051634998b10960e11b81526001600160a01b038981166004830152888116602483015286811660448301529151919092169163933162129160648083019260209291908290030181600087803b158015611f0557600080fd5b505af1158015611f19573d6000803e3d6000fd5b505050506040513d6020811015611f2f57600080fd5b505190505b83811015611f89576040805162461bcd60e51b815260206004820152601e60248201527f526f757465723a20696e73756666696369656e7420616d6f756e744f75740000604482015290519081900360640190fd5b95945050505050565b80471015611fe7576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114612032576040519150601f19603f3d011682016040523d82523d6000602084013e612037565b606091505b5050905080611a5a5760405162461bcd60e51b815260040180806020018281038252603a8152602001806122b5603a913960400191505060405180910390fd5b6060612086848460008561208e565b949350505050565b6060824710156120cf5760405162461bcd60e51b81526004018080602001828103825260268152602001806122ef6026913960400191505060405180910390fd5b6120d8856121ea565b612129576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b602083106121685780518252601f199092019160209182019101612149565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d80600081146121ca576040519150601f19603f3d011682016040523d82523d6000602084013e6121cf565b606091505b50915091506121df8282866121f0565b979650505050505050565b3b151590565b606083156121ff575081611777565b82511561220f5782518084602001fd5b8160405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612259578181015183820152602001612241565b50505050905090810190601f1680156122865780820380516001836020036101000a031916815260200191505b509250505060405180910390fdfe526f757465723a20696e76616c6964205f706174680000000000000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c526f757465723a206d61726b207072696365206c6f776572207468616e206c696d6974cd3829a3813dc3cdd188fd3d01dcf3268c16be2fdd2dd21d0665418816e460625361666545524332303a204552433230206f7065726174696f6e20646964206e6f742073756363656564526f757465723a206d61726b20707269636520686967686572207468616e206c696d6974a26469706673582212205fb07c28854d2a79764cfd95b47807b48d7af91fddcab27d57d2e9b67560698464736f6c634300060c0033000000000000000000000000489ee077994b6658eafa855c308275ead8097c4a00000000000000000000000045096e7aa921f27590f8f19e457794eb0967814100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1 \ No newline at end of file diff --git a/src/Managing.Tools.ABI/Managing.Tools.ABI.csproj b/src/Managing.Tools.ABI/Managing.Tools.ABI.csproj new file mode 100644 index 0000000..86c1b1a --- /dev/null +++ b/src/Managing.Tools.ABI/Managing.Tools.ABI.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/src/Managing.Tools.ABI/OrderBook/ContractDefinition/OrderBookDefinition.cs b/src/Managing.Tools.ABI/OrderBook/ContractDefinition/OrderBookDefinition.cs new file mode 100644 index 0000000..8f3543e --- /dev/null +++ b/src/Managing.Tools.ABI/OrderBook/ContractDefinition/OrderBookDefinition.cs @@ -0,0 +1,1171 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; + +namespace Managing.Tools.OrderBook.ContractDefinition +{ + + + public partial class OrderBookDeployment : OrderBookDeploymentBase + { + public OrderBookDeployment() : base(BYTECODE) { } + public OrderBookDeployment(string byteCode) : base(byteCode) { } + } + + public class OrderBookDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "6080604052600e805460ff1916905534801561001a57600080fd5b506001600055600780546001600160a01b031916331790556153c4806100416000396000f3fe6080604052600436106101c35760003560e01c8062cf066b1461022a578063026032ee1461026f57806307c7edc3146102fa5780630d5cc9381461033d57806311d9444a1461036757806312d43a51146103aa578063269ae6c2146103db5780632b7d6290146104a6578063392e53cd146105465780633fc8cef31461056f57806347e0bbd0146105845780634a686d67146105ae5780634c54f0b0146105c357806363ae21031461062d57806379221fa214610642578063807c5600146106c15780638de10c2e1461087357806395082d25146108885780639983ee1b1461089d5780639e23de5c146108db5780639e71b0f01461090e578063a397ea5414610938578063aec224551461097c578063b142a4b0146109af578063c16cde8a14610a99578063c4a1821b14610ae9578063c86b0f7d14610b99578063cfad57a214610bd7578063d0d40cd614610c0a578063d38ab51914610c9d578063d3bab1d114610ce0578063d566d0ca14610d74578063d7c41c7914610da7578063f2d2e01b14610e00578063f5b91b7b14610e94578063f882ac0714610ea9578063f887ea4014610ed3578063fbfa77cf14610ee8578063fc2cee6214610efd57610225565b36610225576008546001600160a01b03163314610223576040805162461bcd60e51b815260206004820152601960248201527827b93232b92137b7b59d1034b73b30b634b21039b2b73232b960391b604482015290519081900360640190fd5b005b600080fd5b34801561023657600080fd5b5061025d6004803603602081101561024d57600080fd5b50356001600160a01b0316610f27565b60408051918252519081900360200190f35b34801561027b57600080fd5b506102a86004803603604081101561029257600080fd5b506001600160a01b038135169060200135610f39565b604080516001600160a01b03998a168152602081019890985295909716868601526060860193909352901515608085015260a0840152151560c083015260e08201929092529051908190036101000190f35b34801561030657600080fd5b506102236004803603606081101561031d57600080fd5b506001600160a01b0381358116916020810135916040909101351661100c565b34801561034957600080fd5b506102236004803603602081101561036057600080fd5b5035611417565b34801561037357600080fd5b506102236004803603606081101561038a57600080fd5b506001600160a01b038135811691602081013591604090910135166114a8565b3480156103b657600080fd5b506103bf611857565b604080516001600160a01b039092168252519081900360200190f35b61022360048036036101008110156103f257600080fd5b810190602081018135600160201b81111561040c57600080fd5b82018360208201111561041e57600080fd5b803590602001918460208302840111600160201b8311171561043f57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604081013590606081013515159060808101359060a081013515159060c001351515611866565b3480156104b257600080fd5b506104df600480360360408110156104c957600080fd5b506001600160a01b038135169060200135611bef565b604080516001600160a01b039b8c168152998b1660208b015289810198909852958916606089015293909716608087015260a0860191909152151560c085015260e08401949094529215156101008301526101208201929092529051908190036101400190f35b34801561055257600080fd5b5061055b611c61565b604080519115158252519081900360200190f35b34801561057b57600080fd5b506103bf611c6a565b34801561059057600080fd5b50610223600480360360208110156105a757600080fd5b5035611c79565b3480156105ba57600080fd5b5061025d611f83565b3480156105cf57600080fd5b50610614600480360360a08110156105e657600080fd5b5080351515906020810135906001600160a01b03604082013516906060810135151590608001351515611f8f565b6040805192835290151560208301528051918290030190f35b34801561063957600080fd5b5061025d6120f8565b34801561064e57600080fd5b5061067b6004803603604081101561066557600080fd5b506001600160a01b0381351690602001356120fe565b604080516001600160a01b039098168852602088019690965286860194909452606086019290925215156080850152151560a084015260c0830152519081900360e00190f35b3480156106cd57600080fd5b50610223600480360360608110156106e457600080fd5b810190602081018135600160201b8111156106fe57600080fd5b82018360208201111561071057600080fd5b803590602001918460208302840111600160201b8311171561073157600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561078057600080fd5b82018360208201111561079257600080fd5b803590602001918460208302840111600160201b831117156107b357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561080257600080fd5b82018360208201111561081457600080fd5b803590602001918460208302840111600160201b8311171561083557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550612152945050505050565b34801561087f57600080fd5b5061025d6121ea565b34801561089457600080fd5b5061025d6121f0565b3480156108a957600080fd5b50610223600480360360808110156108c057600080fd5b50803590602081013590604081013590606001351515612200565b3480156108e757600080fd5b5061025d600480360360208110156108fe57600080fd5b50356001600160a01b0316612358565b34801561091a57600080fd5b506102236004803603602081101561093157600080fd5b5035612502565b34801561094457600080fd5b50610223600480360360a081101561095b57600080fd5b50803590602081013590604081013590606081013590608001351515612774565b34801561098857600080fd5b5061025d6004803603602081101561099f57600080fd5b50356001600160a01b03166128dc565b61022360048036036101608110156109c657600080fd5b810190602081018135600160201b8111156109e057600080fd5b8201836020820111156109f257600080fd5b803590602001918460208302840111600160201b83111715610a1357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505082359350506001600160a01b036020830135811692604081013592506060810135916080820135169060a081013515159060c08101359060e081013515159061010081013590610120013515156128ee565b610223600480360360e0811015610aaf57600080fd5b506001600160a01b03813581169160208101359160408201351690606081013590608081013515159060a08101359060c001351515612cf0565b348015610af557600080fd5b5061055b60048036036040811015610b0c57600080fd5b810190602081018135600160201b811115610b2657600080fd5b820183602082011115610b3857600080fd5b803590602001918460208302840111600160201b83111715610b5957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250612da1915050565b348015610ba557600080fd5b5061022360048036036080811015610bbc57600080fd5b50803590602081013590604081013590606001351515612fb8565b348015610be357600080fd5b5061022360048036036020811015610bfa57600080fd5b50356001600160a01b0316613181565b348015610c1657600080fd5b50610c4360048036036040811015610c2d57600080fd5b506001600160a01b03813516906020013561322b565b604080516001600160a01b039a8b168152988a1660208a015296909816878701526060870194909452608086019290925260a0850152151560c0840152151560e08301526101008201929092529051908190036101200190f35b348015610ca957600080fd5b5061022360048036036060811015610cc057600080fd5b506001600160a01b038135811691602081013591604090910135166133e8565b348015610cec57600080fd5b50610d1960048036036040811015610d0357600080fd5b506001600160a01b038135169060200135613851565b604080516001600160a01b039a8b1681526020810199909952968916888801529490971660608701526080860192909252151560a085015260c084015292151560e08301526101008201929092529051908190036101200190f35b348015610d8057600080fd5b5061025d60048036036020811015610d9757600080fd5b50356001600160a01b0316613937565b348015610db357600080fd5b50610223600480360360c0811015610dca57600080fd5b506001600160a01b0381358116916020810135821691604082013581169160608101359091169060808101359060a00135613949565b348015610e0c57600080fd5b50610e3960048036036040811015610e2357600080fd5b506001600160a01b038135169060200135613ab8565b604080516001600160a01b039a8b168152988a1660208a0152888101979097529490971660608701526080860192909252151560a085015260c084015292151560e08301526101008201929092529051908190036101200190f35b348015610ea057600080fd5b506103bf613b22565b348015610eb557600080fd5b5061022360048036036020811015610ecc57600080fd5b5035613b31565b348015610edf57600080fd5b506103bf613e6e565b348015610ef457600080fd5b506103bf613e7d565b348015610f0957600080fd5b5061022360048036036020811015610f2057600080fd5b5035613e8c565b60066020526000908152604090205481565b600080600080600080600080610f4d614fd0565b505050506001600160a01b03968716600090815260036020818152604080842099845298815291889020885161012081018a5281548b16815260018201548b1693810184905260028201549981018a9052918101549099166060820181905260048a01546080830181905260058b015460ff908116151560a0850181905260068d015460c0860181905260078e0154909216151560e086018190526008909d0154610100909501859052949c9a9b929a91995093975092955093509150565b60026000541415611052576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b600260005561105f61501c565b6001600160a01b038085166000908152600560209081526040808320878452825291829020825161010081018452815490941684526001810180548451818502810185019095528085529193858401939092908301828280156110eb57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116110cd575b5050509183525050600282015460208201526003820154604082015260048201546060820152600582015460ff8082161515608084015261010090910416151560a082015260069091015460c09091015280519091506001600160a01b0316611189576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b8060a00151156111e0576111a581602001518260800151612da1565b6111e05760405162461bcd60e51b81526004018080602001828103825260268152602001806152f16026913960400191505060405180910390fd5b6001600160a01b0384166000908152600560209081526040808320868452909152812080546001600160a01b03191681559061121f600183018261506e565b50600060028201819055600382018190556004820181905560058201805461ffff191690556006909101819055600b54604083015160208401518051611292946001600160a01b03909416939061127257fe5b60200260200101516001600160a01b0316613f1d9092919063ffffffff16565b600854602082015180516000926001600160a01b0316919060001981019081106112b857fe5b60200260200101516001600160a01b03161480156112d757508160c001515b15611304576112ef8260200151836060015130613f74565b90506112ff8183600001516140a3565b61131e565b61131b826020015183606001518460000151613f74565b90505b61132c8260e00151846140a3565b846001600160a01b03167f7e1fe496989eea92b738a562dbf9c0ae6aa6fcf3f1ef09e95ee4f7603721706b858460200151856040015186606001518688608001518960a001518a60c001518b60e00151604051808a8152602001806020018981526020018881526020018781526020018681526020018515158152602001841515815260200183815260200182810382528a818151815260200191508051906020019060200280838360005b838110156113f05781810151838201526020016113d8565b505050509050019a505050505050505050505060405180910390a250506001600055505050565b6007546001600160a01b0316331461146d576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600d8190556040805182815290517fe46d9daf6d25f7615efa1d0183b90ac6759d85014b598e409aadf0fd918d59a69181900360200190a150565b600260005414156114ee576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000556114fb614fd0565b506001600160a01b03808416600090815260036020818152604080842087855282529283902083516101208101855281548616808252600183015487169382019390935260028201549481019490945291820154909316606083015260048101546080830152600581015460ff908116151560a0840152600682015460c0840152600782015416151560e083015260080154610100820152906115d3576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b60006115f48260e001518360c0015184606001518560a00151156001611f8f565b506001600160a01b0380871660009081526003602081815260408084208a8552825280842080546001600160a01b0319908116825560018201805482169055600282018690559381018054909416909355600480840185905560058401805460ff199081169091556006850186905560078501805490911690556008909301849055600a5488518984015160608b01518b85015160808d015160a08e01518751632662166b60e01b8152958c1699860199909952928a16602485015290891660448401526064830152608482015293151560a48501523060c4850152905195965092949290931692632662166b9260e48084019382900301818787803b1580156116fd57600080fd5b505af1158015611711573d6000803e3d6000fd5b505050506040513d602081101561172757600080fd5b505160085460208501519192506001600160a01b039182169116141561175a576117558184600001516140a3565b611777565b82516020840151611777916001600160a01b039091169083613f1d565b611786836101000151856140a3565b82600001516001600160a01b03167f9a382661d6573da86db000471303be6f0b2b1bb66089b08e3c16a85d7b6e94f88685602001518660400151876060015188608001518960a001518a60c001518b60e001518c61010001518c604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001878152602001861515815260200185815260200184151581526020018381526020018281526020019a505050505050505050505060405180910390a25050600160005550505050565b6007546001600160a01b031681565b600260005414156118ac576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000819055885114806118c2575087516003145b611901576040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b8760018951038151811061191157fe5b60200260200101516001600160a01b03168860008151811061192f57fe5b60200260200101516001600160a01b0316141561198e576040805162461bcd60e51b815260206004820152601860248201527709ee4c8cae484deded67440d2dcecc2d8d2c840bee0c2e8d60431b604482015290519081900360640190fd5b600087116119e2576040805162461bcd60e51b815260206004820152601c60248201527b27b93232b92137b7b59d1034b73b30b634b2102fb0b6b7bab73a24b760211b604482015290519081900360640190fd5b600c54831015611a235760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b611a2b614120565b8115611ae65760085488516001600160a01b03909116908990600090611a4d57fe5b60200260200101516001600160a01b031614611a9a5760405162461bcd60e51b81526004018080602001828103825260258152602001806152606025913960400191505060405180910390fd5b611aa48388614192565b3414611ae15760405162461bcd60e51b81526004018080602001828103825260268152602001806152cb6026913960400191505060405180910390fd5b611bd0565b823414611b245760405162461bcd60e51b815260040180806020018281038252602e815260200180615361602e913960400191505060405180910390fd5b600a5488516001600160a01b0390911690631b827878908a90600090611b4657fe5b602002602001015133308b6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b158015611bb757600080fd5b505af1158015611bcb573d6000803e3d6000fd5b505050505b611be0338989898989878a6141ea565b50506001600055505050505050565b6001602081815260009384526040808520909152918352912080549181015460028201546003830154600484015460058501546006860154600787015460088801546009909801546001600160a01b03998a1699978816989697958616969490951694929360ff92831693919216908a565b600e5460ff1681565b6008546001600160a01b031681565b60026000541415611cbf576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055611ccc61508f565b5033600090815260016020818152604080842085855282529283902083516101408101855281546001600160a01b039081168083529483015481169382019390935260028201549481019490945260038101548216606085015260048101549091166080840152600581015460a0840152600681015460ff908116151560c0850152600782015460e0850152600882015416151561010084015260090154610120830152611daf576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b3360009081526001602081815260408084208685528252832080546001600160a01b0319908116825592810180548416905560028101849055600381018054841690556004810180549093169092556005820183905560068201805460ff19908116909155600783018490556008808401805490921690915560099092019290925554908201516001600160a01b0390811691161415611e7357611e6e611e68826040015183610120015161419290919063ffffffff16565b336140a3565b611ea8565b611e9933826040015183602001516001600160a01b0316613f1d9092919063ffffffff16565b611ea8816101200151336140a3565b80600001516001600160a01b03167fd500f34e0ec655b7614ae42e1d9c666d5e4dde909a1297829f8c5ecf00805d328383602001518460400151856060015186608001518760a001518860c001518960e001518a61010001518b6101200151604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001876001600160a01b03168152602001868152602001851515815260200184815260200183151581526020018281526020019a505050505050505050505060405180910390a250506001600055565b670de0b6b3a764000081565b60008060008461201757600b54604080516340d3096b60e11b81526001600160a01b038981166004830152915191909216916381a612d6916024808301926020929190829003018186803b158015611fe657600080fd5b505afa158015611ffa573d6000803e3d6000fd5b505050506040513d602081101561201057600080fd5b5051612091565b600b5460408051637092736960e11b81526001600160a01b0389811660048301529151919092169163e124e6d2916024808301926020929190829003018186803b15801561206457600080fd5b505afa158015612078573d6000803e3d6000fd5b505050506040513d602081101561208e57600080fd5b50515b90506000886120a2578782106120a6565b8782115b905084156120ea57806120ea5760405162461bcd60e51b81526004018080602001828103825260268152602001806152f16026913960400191505060405180910390fd5b909890975095505050505050565b600c5481565b600560208181526000938452604080852090915291835291208054600282015460038301546004840154948401546006909401546001600160a01b03909316949193909260ff808316926101009004169087565b60005b83518110156121825761217a84828151811061216d57fe5b6020026020010151613b31565b600101612155565b5060005b82518110156121b3576121ab83828151811061219e57fe5b6020026020010151611c79565b600101612186565b5060005b81518110156121e4576121dc8282815181106121cf57fe5b6020026020010151612502565b6001016121b7565b50505050565b600d5481565b68327cb2734119d3b7a9601e1b81565b60026000541415612246576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600160209081526040808320878452909152902080546001600160a01b03166122ae576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b6007810183905560088101805483151560ff19909116811790915560058201859055600382015460048301546006840154604080518a81526001600160a01b039485166020820152929093168284015260ff16151560608201526080810187905260a0810186905260c08101929092525133917f0a0360dd5c354235bbf8d386ba3b24ef8134088e0785677de1504df219d9149a919081900360e00190a250506001600055505050565b600b5460408051632c668ec160e01b81526001600160a01b038481166004830152670de0b6b3a76400006024830152915160009384931691632c668ec1916044808301926020929190829003018186803b1580156123b557600080fd5b505afa1580156123c9573d6000803e3d6000fd5b505050506040513d60208110156123df57600080fd5b5051600b54604080516340d3096b60e11b81526001600160a01b038781166004830152915193945060009391909216916381a612d6916024808301926020929190829003018186803b15801561243457600080fd5b505afa158015612448573d6000803e3d6000fd5b505050506040513d602081101561245e57600080fd5b5051600b54604080516323b95ceb60e21b81526001600160a01b03888116600483015291519394506000939190921691638ee573ac916024808301926020929190829003018186803b1580156124b357600080fd5b505afa1580156124c7573d6000803e3d6000fd5b505050506040513d60208110156124dd57600080fd5b505190506124f9600a82900a6124f385856143f9565b90614452565b95945050505050565b60026000541415612548576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612555614fd0565b5033600090815260036020818152604080842085855282529283902083516101208101855281546001600160a01b03908116808352600184015482169483019490945260028301549582019590955292810154909316606083015260048301546080830152600583015460ff908116151560a0840152600684015460c0840152600784015416151560e08301526008909201546101008201529061262e576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b336000818152600360208181526040808420878552909152822080546001600160a01b03199081168255600182018054821690556002820184905591810180549092169091556004810182905560058101805460ff19908116909155600682018390556007820180549091169055600801556101008201516126af916140a3565b80600001516001600160a01b03167f1154174c82984656b028c8021671988f60a346497e56fe02554761184f82a0758383602001518460400151856060015186608001518760a001518860c001518960e001518a6101000151604051808a8152602001896001600160a01b03168152602001888152602001876001600160a01b0316815260200186815260200185151581526020018481526020018315158152602001828152602001995050505050505050505060405180910390a250506001600055565b600260005414156127ba576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600360209081526040808320888452909152902080546001600160a01b0316612822576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b6006810183905560078101805483151560ff1990911681179091556004820185905560028201869055600182015460038301546005840154604080518b81526001600160a01b0394851660208201528082018b90529290931660608301526080820188905260ff16151560a082015260c0810186905260e08101929092525133917f75781255bc71c83f89f29e5a2599f2c174a562d2cd8f2e818a47f132e728049891908190036101000190a25050600160005550505050565b60026020526000908152604090205481565b60026000541415612934576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612941614120565b600c548210156129825760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b8015612a3d576008548b516001600160a01b03909116908c906000906129a457fe5b60200260200101516001600160a01b0316146129f15760405162461bcd60e51b81526004018080602001828103825260258152602001806152606025913960400191505060405180910390fd5b6129fb828b614192565b3414612a385760405162461bcd60e51b81526004018080602001828103825260268152602001806152cb6026913960400191505060405180910390fd5b612b27565b813414612a7b5760405162461bcd60e51b815260040180806020018281038252602e815260200180615361602e913960400191505060405180910390fd5b600a548b516001600160a01b0390911690631b827878908d90600090612a9d57fe5b602002602001015133308e6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b158015612b0e57600080fd5b505af1158015612b22573d6000803e3d6000fd5b505050505b60008b60018d510381518110612b3957fe5b60200260200101519050600060018d511115612bfe57816001600160a01b03168d600081518110612b6657fe5b60200260200101516001600160a01b03161415612bc5576040805162461bcd60e51b815260206004820152601860248201527709ee4c8cae484deded67440d2dcecc2d8d2c840bee0c2e8d60431b604482015290519081900360640190fd5b612bec600b60009054906101000a90046001600160a01b03168d8f60008151811061127257fe5b612bf78d8b30613f74565b9050612c01565b508a5b600b5460408051630a48d5a960e01b81526001600160a01b0385811660048301526024820185905291516000939290921691630a48d5a991604480820192602092909190829003018186803b158015612c5957600080fd5b505afa158015612c6d573d6000803e3d6000fd5b505050506040513d6020811015612c8357600080fd5b5051600d54909150811015612cc95760405162461bcd60e51b81526004018080602001828103825260228152602001806151bd6022913960400191505060405180910390fd5b50612cdc3383838b8f8e8d8d8d8d614491565b505060016000555050505050505050505050565b60026000541415612d36576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055612d43614120565b600c543411612d835760405162461bcd60e51b81526004018080602001828103825260258152602001806152856025913960400191505060405180910390fd5b612d933386868a8a888888614764565b505060016000555050505050565b6000825160021480612db4575082516003145b612df3576040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b600083600081518110612e0257fe5b60200260200101519050600084600186510381518110612e1e57fe5b602090810291909101015160095490915060009081906001600160a01b0385811691161415612e6b57612e6487600181518110612e5757fe5b6020026020010151612358565b9150612ee7565b600b54604080516340d3096b60e11b81526001600160a01b038781166004830152915191909216916381a612d6916024808301926020929190829003018186803b158015612eb857600080fd5b505afa158015612ecc573d6000803e3d6000fd5b505050506040513d6020811015612ee257600080fd5b505191505b6009546001600160a01b0384811691161415612f10575068327cb2734119d3b7a9601e1b612f8c565b600b5460408051637092736960e11b81526001600160a01b0386811660048301529151919092169163e124e6d2916024808301926020929190829003018186803b158015612f5d57600080fd5b505afa158015612f71573d6000803e3d6000fd5b505050506040513d6020811015612f8757600080fd5b505190505b6000612fa8836124f38468327cb2734119d3b7a9601e1b6143f9565b8710955050505050505b92915050565b60026000541415612ffe576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b60026000908155338152600560209081526040808320878452909152902080546001600160a01b0316613066576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b838160030181905550828160040181905550818160050160006101000a81548160ff021916908315150217905550336001600160a01b03167fa7f9f4a25eb76f5ec01b1a429d95d6a00833f0f137c88827c58799a1c1ff0dfe868360010184600201548888888860050160019054906101000a900460ff168960060154604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818154815260200191508054801561316057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613142575b5050995050505050505050505060405180910390a250506001600055505050565b6007546001600160a01b031633146131d7576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600780546001600160a01b0383166001600160a01b0319909116811790915560408051918252517fe24c39186e9137521953beaa8446e71f55b8f12296984f9d4273ceb1af728d909181900360200190a150565b600080600080600080600080600061324161501c565b6001600160a01b03808d1660009081526005602090815260408083208f8452825291829020825161010081018452815490941684526001810180548451818502810185019095528085529193858401939092908301828280156132cd57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116132af575b505050918352505060028201546020808301919091526003830154604083015260048301546060830152600583015460ff8082161515608085015261010090910416151560a083015260069092015460c0909101528101515190915061333457600061334e565b806020015160008151811061334557fe5b60200260200101515b60018260200151511161336257600061337c565b816020015160018151811061337357fe5b60200260200101515b6002836020015151116133905760006133aa565b82602001516002815181106133a157fe5b60200260200101515b8360400151846060015185608001518660a001518760c001518860e00151995099509950995099509950995099509950509295985092959850929598565b6002600054141561342e576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b600260005561343b61508f565b506001600160a01b038084166000908152600160208181526040808420878552825292839020835161014081018552815486168082529382015486169281019290925260028101549382019390935260038301548416606082015260048301549093166080840152600582015460a0840152600682015460ff908116151560c0850152600783015460e08501526008830154161515610100840152600990910154610120830152613521576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b60006135428261010001518360e0015184608001518560c001516001611f8f565b506001600160a01b0380871660009081526001602081815260408084208a8552825280842080546001600160a01b0319908116825593810180548516905560028101859055600381018054851690556004810180549094169093556005830184905560068301805460ff19908116909155600784018590556008840180549091169055600990920192909255600b5490860151918601519394506135ea938316921690613f1d565b81606001516001600160a01b031682602001516001600160a01b0316146136c257604080516002808252606080830184529260208301908036833701905050905082602001518160008151811061363d57fe5b60200260200101906001600160a01b031690816001600160a01b03168152505082606001518160018151811061366f57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050600061369d82600030613f74565b600b5460608601519192506136bf916001600160a01b03908116911683613f1d565b50505b600a5482516060840151608085015160a086015160c087015160408051630f8ee8bb60e11b81526001600160a01b03968716600482015294861660248601529285166044850152606484019190915215156084830152519190921691631f1dd1769160a480830192600092919082900301818387803b15801561374457600080fd5b505af1158015613758573d6000803e3d6000fd5b5050505061376b826101200151846140a3565b81600001516001600160a01b03167f7fb1c74d1ea6aa1c9c585e17ce8274c8ff98745e85e7459b73f87d784494f58e8584602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001518c604051808c81526020018b6001600160a01b031681526020018a8152602001896001600160a01b03168152602001886001600160a01b03168152602001878152602001861515815260200185815260200184151581526020018381526020018281526020019b50505050505050505050505060405180910390a250506001600055505050565b600080600080600080600080600061386761508f565b505050506001600160a01b0397881660009081526001602081815260408084209a845299815291899020895161014081018b5281548c168152918101548b1692820183905260028101549982018a905260038101548b16606083018190526004820154909b1660808301819052600582015460a08401819052600683015460ff908116151560c08601819052600785015460e08701819052600886015490921615156101008701819052600990950154610120909601869052959e9c9d9c929b5090995093975092955093509150565b60046020526000908152604090205481565b6007546001600160a01b0316331461399f576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600e5460ff16156139f7576040805162461bcd60e51b815260206004820152601e60248201527f4f72646572426f6f6b3a20616c726561647920696e697469616c697a65640000604482015290519081900360640190fd5b600e805460ff19166001179055600a80546001600160a01b038089166001600160a01b03199283168117909355600b8054898316908416811790915560088054898416908516811790915560098054938916939094168317909355600c869055600d8590556040805194855260208501919091528381019290925260608301526080820184905260a08201839052517fcfb7ef8749fafc8da2af1ba3d025479ffc4e58f7dc420113e112512a3bda59639181900360c00190a1505050505050565b600360208181526000938452604080852090915291835291208054600182015460028301549383015460048401546005850154600686015460078701546008909701546001600160a01b039687169895871697959690941694929360ff9283169391929091169089565b6009546001600160a01b031681565b60026000541415613b77576040805162461bcd60e51b815260206004820152601f602482015260008051602061519d833981519152604482015290519081900360640190fd5b6002600055613b8461501c565b33600090815260056020908152604080832085845282529182902082516101008101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015613c0d57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613bef575b5050509183525050600282015460208201526003820154604082015260048201546060820152600582015460ff8082161515608084015261010090910416151560a082015260069091015460c09091015280519091506001600160a01b0316613cab576040805162461bcd60e51b815260206004820152601d602482015260008051602061517d833981519152604482015290519081900360640190fd5b336000908152600560209081526040808320858452909152812080546001600160a01b031916815590613ce1600183018261506e565b50600060028201819055600382018190556004820181905560058201805461ffff191690556006909101819055600854602083015180516001600160a01b03909216929091613d2c57fe5b60200260200101516001600160a01b03161415613d6657613d61611e6882604001518360e0015161419290919063ffffffff16565b613d8e565b613d80338260400151836020015160008151811061127257fe5b613d8e8160e00151336140a3565b336001600160a01b03167fefd66d4f9c2f880c70aedeb5b26a44fb474cea07e5d6c533f2d27c303d5d94538383602001518460400151856060015186608001518760a001518860c001518960e00151604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818151815260200191508051906020019060200280838360005b83811015613e4b578181015183820152602001613e33565b50505050905001995050505050505050505060405180910390a250506001600055565b600a546001600160a01b031681565b600b546001600160a01b031681565b6007546001600160a01b03163314613ee2576040805162461bcd60e51b815260206004820152601460248201527327b93232b92137b7b59d103337b93134b23232b760611b604482015290519081900360640190fd5b600c8190556040805182815290517fbde5eafdc37b81830d70124cddccaaa6d034e71dda3c8fc18a959ca76a7cbcfc9181900360200190a150565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052613f6f9084906149da565b505050565b6000835160021415613fbb57613fb484600081518110613f9057fe5b602002602001015185600181518110613fa557fe5b60200260200101518585614a8b565b905061409c565b835160031415614061576000613ffc85600081518110613fd757fe5b602002602001015186600181518110613fec57fe5b6020026020010151600030614a8b565b9050614025600b60009054906101000a90046001600160a01b0316828760018151811061127257fe5b6140598560018151811061403557fe5b60200260200101518660028151811061404a57fe5b60200260200101518686614a8b565b91505061409c565b6040805162461bcd60e51b815260206004820152601f6024820152600080516020615317833981519152604482015290519081900360640190fd5b9392505050565b60085460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156140f057600080fd5b505af1158015614104573d6000803e3d6000fd5b5061411c925050506001600160a01b03821683614c6a565b5050565b341561419057600860009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561417657600080fd5b505af115801561418a573d6000803e3d6000fd5b50505050505b565b60008282018381101561409c576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6001600160a01b03881660009081526006602052604090205461420b61501c565b6040518061010001604052808b6001600160a01b031681526020018a81526020018981526020018881526020018781526020018615158152602001851515815260200184815250905061426860018361419290919063ffffffff16565b6001600160a01b038b8116600090815260066020908152604080832094909455600581528382208683528152929020835181546001600160a01b031916921691909117815582820151805184936142c69260018501929101906150e3565b5060408201518160020155606082015181600301556080820151816004015560a08201518160050160006101000a81548160ff02191690831515021790555060c08201518160050160016101000a81548160ff02191690831515021790555060e08201518160060155905050896001600160a01b03167fdf06bb56ffc4029dc0b62b68bb5bbadea93a38b530cefc9b81afb742a6555d88838b8b8b8b8b8b8b604051808981526020018060200188815260200187815260200186815260200185151581526020018415158152602001838152602001828103825289818151815260200191508051906020019060200280838360005b838110156143d35781810151838201526020016143bb565b50505050905001995050505050505050505060405180910390a250505050505050505050565b60008261440857506000612fb2565b8282028284828161441557fe5b041461409c5760405162461bcd60e51b81526004018080602001828103825260218152602001806152aa6021913960400191505060405180910390fd5b600061409c83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250614d4f565b336000908152600260205260409020546144a961508f565b6040518061014001604052808d6001600160a01b031681526020018c6001600160a01b031681526020018b81526020018a6001600160a01b03168152602001896001600160a01b031681526020018881526020018715158152602001868152602001851515815260200184815250905061452d60018361419290919063ffffffff16565b600260008e6001600160a01b03166001600160a01b031681526020019081526020016000208190555080600160008e6001600160a01b03166001600160a01b03168152602001908152602001600020600084815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816002015560608201518160030160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060808201518160040160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060a0820151816005015560c08201518160060160006101000a81548160ff02191690831515021790555060e082015181600701556101008201518160080160006101000a81548160ff02191690831515021790555061012082015181600901559050508b6001600160a01b03167fb27b9afe3043b93788c40cfc3cc73f5d928a2e40f3ba01820b246426de8fa1b9838d8d8d8d8d8d8d8d8d604051808b81526020018a6001600160a01b03168152602001898152602001886001600160a01b03168152602001876001600160a01b03168152602001868152602001851515815260200184815260200183151581526020018281526020019a505050505050505050505060405180910390a2505050505050505050505050565b6001600160a01b038816600090815260046020526040902054614785614fd0565b5060408051610120810182526001600160a01b03808c1682528a8116602083015291810189905290871660608201526080810186905284151560a082015260c0810184905282151560e0820152346101008201526147e4826001614192565b600460008c6001600160a01b03166001600160a01b031681526020019081526020016000208190555080600360008c6001600160a01b03166001600160a01b03168152602001908152602001600020600084815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816002015560608201518160030160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816004015560a08201518160050160006101000a81548160ff02191690831515021790555060c0820151816006015560e08201518160070160006101000a81548160ff0219169083151502179055506101008201518160080155905050896001600160a01b03167f48ee333d2a65cc45fdb83bc012920d89181c3377390cd239d2b63f2bef67a02d838b8b8b8b8b8b8b34604051808a8152602001896001600160a01b03168152602001888152602001876001600160a01b0316815260200186815260200185151581526020018481526020018315158152602001828152602001995050505050505050505060405180910390a250505050505050505050565b6060614a2f826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614df19092919063ffffffff16565b805190915015613f6f57808060200190516020811015614a4e57600080fd5b5051613f6f5760405162461bcd60e51b815260040180806020018281038252602a815260200180615337602a913960400191505060405180910390fd5b60095460009081906001600160a01b0386811691161415614b3057600b546040805163817bb85760e01b81526001600160a01b03898116600483015286811660248301529151919092169163817bb8579160448083019260209291908290030181600087803b158015614afd57600080fd5b505af1158015614b11573d6000803e3d6000fd5b505050506040513d6020811015614b2757600080fd5b50519050614c2b565b6009546001600160a01b0387811691161415614b9d57600b5460408051630711e61960e41b81526001600160a01b03888116600483015286811660248301529151919092169163711e61909160448083019260209291908290030181600087803b158015614afd57600080fd5b600b5460408051634998b10960e11b81526001600160a01b038981166004830152888116602483015286811660448301529151919092169163933162129160648083019260209291908290030181600087803b158015614bfc57600080fd5b505af1158015614c10573d6000803e3d6000fd5b505050506040513d6020811015614c2657600080fd5b505190505b838110156124f95760405162461bcd60e51b815260040180806020018281038252602181526020018061523f6021913960400191505060405180910390fd5b80471015614cbf576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114614d0a576040519150601f19603f3d011682016040523d82523d6000602084013e614d0f565b606091505b5050905080613f6f5760405162461bcd60e51b815260040180806020018281038252603a8152602001806151df603a913960400191505060405180910390fd5b60008183614ddb5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015614da0578181015183820152602001614d88565b50505050905090810190601f168015614dcd5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506000838581614de757fe5b0495945050505050565b6060614e008484600085614e08565b949350505050565b606082471015614e495760405162461bcd60e51b81526004018080602001828103825260268152602001806152196026913960400191505060405180910390fd5b614e5285614f64565b614ea3576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b60208310614ee25780518252601f199092019160209182019101614ec3565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114614f44576040519150601f19603f3d011682016040523d82523d6000602084013e614f49565b606091505b5091509150614f59828286614f6a565b979650505050505050565b3b151590565b60608315614f7957508161409c565b825115614f895782518084602001fd5b60405162461bcd60e51b8152602060048201818152845160248401528451859391928392604401919085019080838360008315614da0578181015183820152602001614d88565b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915290565b60405180610100016040528060006001600160a01b0316815260200160608152602001600081526020016000815260200160008152602001600015158152602001600015158152602001600081525090565b508054600082559060005260206000209081019061508c9190615148565b50565b6040805161014081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081019190915290565b828054828255906000526020600020908101928215615138579160200282015b8281111561513857825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190615103565b5061514492915061515d565b5090565b5b808211156151445760008155600101615149565b5b808211156151445780546001600160a01b031916815560010161515e56fe4f72646572426f6f6b3a206e6f6e2d6578697374656e74206f726465720000005265656e7472616e637947756172643a207265656e7472616e742063616c6c004f72646572426f6f6b3a20696e73756666696369656e7420636f6c6c61746572616c416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c4f72646572426f6f6b3a20696e73756666696369656e7420616d6f756e744f75744f72646572426f6f6b3a206f6e6c79207765746820636f756c6420626520777261707065644f72646572426f6f6b3a20696e73756666696369656e7420657865637574696f6e20666565536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774f72646572426f6f6b3a20696e636f72726563742076616c7565207472616e736665727265644f72646572426f6f6b3a20696e76616c696420707269636520666f7220657865637574696f6e4f72646572426f6f6b3a20696e76616c6964205f706174682e6c656e677468005361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565644f72646572426f6f6b3a20696e636f727265637420657865637574696f6e20666565207472616e73666572726564a2646970667358221220e85a1f264782d8faab5f0f3dab3cf5b8844d298c594ad34ab45699fcaa9855e464736f6c634300060c0033"; + public OrderBookDeploymentBase() : base(BYTECODE) { } + public OrderBookDeploymentBase(string byteCode) : base(byteCode) { } + + } + + public partial class PricePrecisionFunction : PricePrecisionFunctionBase { } + + [Function("PRICE_PRECISION", "uint256")] + public class PricePrecisionFunctionBase : FunctionMessage + { + + } + + public partial class UsdgPrecisionFunction : UsdgPrecisionFunctionBase { } + + [Function("USDG_PRECISION", "uint256")] + public class UsdgPrecisionFunctionBase : FunctionMessage + { + + } + + public partial class CancelDecreaseOrderFunction : CancelDecreaseOrderFunctionBase { } + + [Function("cancelDecreaseOrder")] + public class CancelDecreaseOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class CancelIncreaseOrderFunction : CancelIncreaseOrderFunctionBase { } + + [Function("cancelIncreaseOrder")] + public class CancelIncreaseOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class CancelMultipleFunction : CancelMultipleFunctionBase { } + + [Function("cancelMultiple")] + public class CancelMultipleFunctionBase : FunctionMessage + { + [Parameter("uint256[]", "_swapOrderIndexes", 1)] + public virtual List SwapOrderIndexes { get; set; } + [Parameter("uint256[]", "_increaseOrderIndexes", 2)] + public virtual List IncreaseOrderIndexes { get; set; } + [Parameter("uint256[]", "_decreaseOrderIndexes", 3)] + public virtual List DecreaseOrderIndexes { get; set; } + } + + public partial class CancelSwapOrderFunction : CancelSwapOrderFunctionBase { } + + [Function("cancelSwapOrder")] + public class CancelSwapOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class CreateDecreaseOrderFunction : CreateDecreaseOrderFunctionBase { } + + [Function("createDecreaseOrder")] + public class CreateDecreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_indexToken", 1)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_sizeDelta", 2)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("address", "_collateralToken", 3)] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "_collateralDelta", 4)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_triggerPrice", 6)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 7)] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class CreateIncreaseOrderFunction : CreateIncreaseOrderFunctionBase { } + + [Function("createIncreaseOrder")] + public class CreateIncreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_amountIn", 2)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("address", "_indexToken", 3)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_minOut", 4)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("address", "_collateralToken", 6)] + public virtual string CollateralToken { get; set; } + [Parameter("bool", "_isLong", 7)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_triggerPrice", 8)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 9)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "_executionFee", 10)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("bool", "_shouldWrap", 11)] + public virtual bool ShouldWrap { get; set; } + } + + public partial class CreateSwapOrderFunction : CreateSwapOrderFunctionBase { } + + [Function("createSwapOrder")] + public class CreateSwapOrderFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_amountIn", 2)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "_minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_triggerRatio", 4)] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 5)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "_executionFee", 6)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("bool", "_shouldWrap", 7)] + public virtual bool ShouldWrap { get; set; } + [Parameter("bool", "_shouldUnwrap", 8)] + public virtual bool ShouldUnwrap { get; set; } + } + + public partial class DecreaseOrdersFunction : DecreaseOrdersFunctionBase { } + + [Function("decreaseOrders", typeof(DecreaseOrdersOutputDTO))] + public class DecreaseOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + } + + public partial class DecreaseOrdersIndexFunction : DecreaseOrdersIndexFunctionBase { } + + [Function("decreaseOrdersIndex", "uint256")] + public class DecreaseOrdersIndexFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class ExecuteDecreaseOrderFunction : ExecuteDecreaseOrderFunctionBase { } + + [Function("executeDecreaseOrder")] + public class ExecuteDecreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_address", 1)] + public virtual string Address { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "_feeReceiver", 3)] + public virtual string FeeReceiver { get; set; } + } + + public partial class ExecuteIncreaseOrderFunction : ExecuteIncreaseOrderFunctionBase { } + + [Function("executeIncreaseOrder")] + public class ExecuteIncreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_address", 1)] + public virtual string Address { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "_feeReceiver", 3)] + public virtual string FeeReceiver { get; set; } + } + + public partial class ExecuteSwapOrderFunction : ExecuteSwapOrderFunctionBase { } + + [Function("executeSwapOrder")] + public class ExecuteSwapOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "_feeReceiver", 3)] + public virtual string FeeReceiver { get; set; } + } + + public partial class GetDecreaseOrderFunction : GetDecreaseOrderFunctionBase { } + + [Function("getDecreaseOrder", typeof(GetDecreaseOrderOutputDTO))] + public class GetDecreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class GetIncreaseOrderFunction : GetIncreaseOrderFunctionBase { } + + [Function("getIncreaseOrder", typeof(GetIncreaseOrderOutputDTO))] + public class GetIncreaseOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class GetSwapOrderFunction : GetSwapOrderFunctionBase { } + + [Function("getSwapOrder", typeof(GetSwapOrderOutputDTO))] + public class GetSwapOrderFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "_orderIndex", 2)] + public virtual BigInteger OrderIndex { get; set; } + } + + public partial class GetUsdgMinPriceFunction : GetUsdgMinPriceFunctionBase { } + + [Function("getUsdgMinPrice", "uint256")] + public class GetUsdgMinPriceFunctionBase : FunctionMessage + { + [Parameter("address", "_otherToken", 1)] + public virtual string OtherToken { get; set; } + } + + public partial class GovFunction : GovFunctionBase { } + + [Function("gov", "address")] + public class GovFunctionBase : FunctionMessage + { + + } + + public partial class IncreaseOrdersFunction : IncreaseOrdersFunctionBase { } + + [Function("increaseOrders", typeof(IncreaseOrdersOutputDTO))] + public class IncreaseOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + } + + public partial class IncreaseOrdersIndexFunction : IncreaseOrdersIndexFunctionBase { } + + [Function("increaseOrdersIndex", "uint256")] + public class IncreaseOrdersIndexFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class InitializeFunction : InitializeFunctionBase { } + + [Function("initialize")] + public class InitializeFunctionBase : FunctionMessage + { + [Parameter("address", "_router", 1)] + public virtual string Router { get; set; } + [Parameter("address", "_vault", 2)] + public virtual string Vault { get; set; } + [Parameter("address", "_weth", 3)] + public virtual string Weth { get; set; } + [Parameter("address", "_usdg", 4)] + public virtual string Usdg { get; set; } + [Parameter("uint256", "_minExecutionFee", 5)] + public virtual BigInteger MinExecutionFee { get; set; } + [Parameter("uint256", "_minPurchaseTokenAmountUsd", 6)] + public virtual BigInteger MinPurchaseTokenAmountUsd { get; set; } + } + + public partial class IsInitializedFunction : IsInitializedFunctionBase { } + + [Function("isInitialized", "bool")] + public class IsInitializedFunctionBase : FunctionMessage + { + + } + + public partial class MinExecutionFeeFunction : MinExecutionFeeFunctionBase { } + + [Function("minExecutionFee", "uint256")] + public class MinExecutionFeeFunctionBase : FunctionMessage + { + + } + + public partial class MinPurchaseTokenAmountUsdFunction : MinPurchaseTokenAmountUsdFunctionBase { } + + [Function("minPurchaseTokenAmountUsd", "uint256")] + public class MinPurchaseTokenAmountUsdFunctionBase : FunctionMessage + { + + } + + public partial class RouterFunction : RouterFunctionBase { } + + [Function("router", "address")] + public class RouterFunctionBase : FunctionMessage + { + + } + + public partial class SetGovFunction : SetGovFunctionBase { } + + [Function("setGov")] + public class SetGovFunctionBase : FunctionMessage + { + [Parameter("address", "_gov", 1)] + public virtual string Gov { get; set; } + } + + public partial class SetMinExecutionFeeFunction : SetMinExecutionFeeFunctionBase { } + + [Function("setMinExecutionFee")] + public class SetMinExecutionFeeFunctionBase : FunctionMessage + { + [Parameter("uint256", "_minExecutionFee", 1)] + public virtual BigInteger MinExecutionFee { get; set; } + } + + public partial class SetMinPurchaseTokenAmountUsdFunction : SetMinPurchaseTokenAmountUsdFunctionBase { } + + [Function("setMinPurchaseTokenAmountUsd")] + public class SetMinPurchaseTokenAmountUsdFunctionBase : FunctionMessage + { + [Parameter("uint256", "_minPurchaseTokenAmountUsd", 1)] + public virtual BigInteger MinPurchaseTokenAmountUsd { get; set; } + } + + public partial class SwapOrdersFunction : SwapOrdersFunctionBase { } + + [Function("swapOrders", typeof(SwapOrdersOutputDTO))] + public class SwapOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + } + + public partial class SwapOrdersIndexFunction : SwapOrdersIndexFunctionBase { } + + [Function("swapOrdersIndex", "uint256")] + public class SwapOrdersIndexFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class UpdateDecreaseOrderFunction : UpdateDecreaseOrderFunctionBase { } + + [Function("updateDecreaseOrder")] + public class UpdateDecreaseOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("uint256", "_collateralDelta", 2)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 3)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("uint256", "_triggerPrice", 4)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 5)] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class UpdateIncreaseOrderFunction : UpdateIncreaseOrderFunctionBase { } + + [Function("updateIncreaseOrder")] + public class UpdateIncreaseOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("uint256", "_sizeDelta", 2)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("uint256", "_triggerPrice", 3)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 4)] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class UpdateSwapOrderFunction : UpdateSwapOrderFunctionBase { } + + [Function("updateSwapOrder")] + public class UpdateSwapOrderFunctionBase : FunctionMessage + { + [Parameter("uint256", "_orderIndex", 1)] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("uint256", "_minOut", 2)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_triggerRatio", 3)] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "_triggerAboveThreshold", 4)] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class UsdgFunction : UsdgFunctionBase { } + + [Function("usdg", "address")] + public class UsdgFunctionBase : FunctionMessage + { + + } + + public partial class ValidatePositionOrderPriceFunction : ValidatePositionOrderPriceFunctionBase { } + + [Function("validatePositionOrderPrice", typeof(ValidatePositionOrderPriceOutputDTO))] + public class ValidatePositionOrderPriceFunctionBase : FunctionMessage + { + [Parameter("bool", "_triggerAboveThreshold", 1)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "_triggerPrice", 2)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("address", "_indexToken", 3)] + public virtual string IndexToken { get; set; } + [Parameter("bool", "_maximizePrice", 4)] + public virtual bool MaximizePrice { get; set; } + [Parameter("bool", "_raise", 5)] + public virtual bool Raise { get; set; } + } + + public partial class ValidateSwapOrderPriceWithTriggerAboveThresholdFunction : ValidateSwapOrderPriceWithTriggerAboveThresholdFunctionBase { } + + [Function("validateSwapOrderPriceWithTriggerAboveThreshold", "bool")] + public class ValidateSwapOrderPriceWithTriggerAboveThresholdFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_triggerRatio", 2)] + public virtual BigInteger TriggerRatio { get; set; } + } + + public partial class VaultFunction : VaultFunctionBase { } + + [Function("vault", "address")] + public class VaultFunctionBase : FunctionMessage + { + + } + + public partial class WethFunction : WethFunctionBase { } + + [Function("weth", "address")] + public class WethFunctionBase : FunctionMessage + { + + } + + public partial class CancelDecreaseOrderEventDTO : CancelDecreaseOrderEventDTOBase { } + + [Event("CancelDecreaseOrder")] + public class CancelDecreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "collateralToken", 3, false )] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 5, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 8, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 9, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class CancelIncreaseOrderEventDTO : CancelIncreaseOrderEventDTOBase { } + + [Event("CancelIncreaseOrder")] + public class CancelIncreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "purchaseToken", 3, false )] + public virtual string PurchaseToken { get; set; } + [Parameter("uint256", "purchaseTokenAmount", 4, false )] + public virtual BigInteger PurchaseTokenAmount { get; set; } + [Parameter("address", "collateralToken", 5, false )] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 6, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 7, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 8, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 9, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 10, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 11, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class CancelSwapOrderEventDTO : CancelSwapOrderEventDTOBase { } + + [Event("CancelSwapOrder")] + public class CancelSwapOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address[]", "path", 3, false )] + public virtual List Path { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "triggerRatio", 6, false )] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 7, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 8, false )] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class CreateDecreaseOrderEventDTO : CreateDecreaseOrderEventDTOBase { } + + [Event("CreateDecreaseOrder")] + public class CreateDecreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "collateralToken", 3, false )] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 5, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 8, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 9, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class CreateIncreaseOrderEventDTO : CreateIncreaseOrderEventDTOBase { } + + [Event("CreateIncreaseOrder")] + public class CreateIncreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "purchaseToken", 3, false )] + public virtual string PurchaseToken { get; set; } + [Parameter("uint256", "purchaseTokenAmount", 4, false )] + public virtual BigInteger PurchaseTokenAmount { get; set; } + [Parameter("address", "collateralToken", 5, false )] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 6, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 7, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 8, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 9, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 10, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 11, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class CreateSwapOrderEventDTO : CreateSwapOrderEventDTOBase { } + + [Event("CreateSwapOrder")] + public class CreateSwapOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address[]", "path", 3, false )] + public virtual List Path { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "triggerRatio", 6, false )] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 7, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 8, false )] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class ExecuteDecreaseOrderEventDTO : ExecuteDecreaseOrderEventDTOBase { } + + [Event("ExecuteDecreaseOrder")] + public class ExecuteDecreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "collateralToken", 3, false )] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 5, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 8, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 9, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "executionPrice", 11, false )] + public virtual BigInteger ExecutionPrice { get; set; } + } + + public partial class ExecuteIncreaseOrderEventDTO : ExecuteIncreaseOrderEventDTOBase { } + + [Event("ExecuteIncreaseOrder")] + public class ExecuteIncreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "purchaseToken", 3, false )] + public virtual string PurchaseToken { get; set; } + [Parameter("uint256", "purchaseTokenAmount", 4, false )] + public virtual BigInteger PurchaseTokenAmount { get; set; } + [Parameter("address", "collateralToken", 5, false )] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 6, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 7, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 8, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 9, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 10, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 11, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "executionPrice", 12, false )] + public virtual BigInteger ExecutionPrice { get; set; } + } + + public partial class ExecuteSwapOrderEventDTO : ExecuteSwapOrderEventDTOBase { } + + [Event("ExecuteSwapOrder")] + public class ExecuteSwapOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address[]", "path", 3, false )] + public virtual List Path { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "amountOut", 6, false )] + public virtual BigInteger AmountOut { get; set; } + [Parameter("uint256", "triggerRatio", 7, false )] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 8, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 9, false )] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class InitializeEventDTO : InitializeEventDTOBase { } + + [Event("Initialize")] + public class InitializeEventDTOBase : IEventDTO + { + [Parameter("address", "router", 1, false )] + public virtual string Router { get; set; } + [Parameter("address", "vault", 2, false )] + public virtual string Vault { get; set; } + [Parameter("address", "weth", 3, false )] + public virtual string Weth { get; set; } + [Parameter("address", "usdg", 4, false )] + public virtual string Usdg { get; set; } + [Parameter("uint256", "minExecutionFee", 5, false )] + public virtual BigInteger MinExecutionFee { get; set; } + [Parameter("uint256", "minPurchaseTokenAmountUsd", 6, false )] + public virtual BigInteger MinPurchaseTokenAmountUsd { get; set; } + } + + public partial class UpdateDecreaseOrderEventDTO : UpdateDecreaseOrderEventDTOBase { } + + [Event("UpdateDecreaseOrder")] + public class UpdateDecreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "collateralToken", 3, false )] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 5, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 8, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 9, false )] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class UpdateGovEventDTO : UpdateGovEventDTOBase { } + + [Event("UpdateGov")] + public class UpdateGovEventDTOBase : IEventDTO + { + [Parameter("address", "gov", 1, false )] + public virtual string Gov { get; set; } + } + + public partial class UpdateIncreaseOrderEventDTO : UpdateIncreaseOrderEventDTOBase { } + + [Event("UpdateIncreaseOrder")] + public class UpdateIncreaseOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "orderIndex", 2, false )] + public virtual BigInteger OrderIndex { get; set; } + [Parameter("address", "collateralToken", 3, false )] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 4, false )] + public virtual string IndexToken { get; set; } + [Parameter("bool", "isLong", 5, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("uint256", "triggerPrice", 7, false )] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 8, false )] + public virtual bool TriggerAboveThreshold { get; set; } + } + + public partial class UpdateMinExecutionFeeEventDTO : UpdateMinExecutionFeeEventDTOBase { } + + [Event("UpdateMinExecutionFee")] + public class UpdateMinExecutionFeeEventDTOBase : IEventDTO + { + [Parameter("uint256", "minExecutionFee", 1, false )] + public virtual BigInteger MinExecutionFee { get; set; } + } + + public partial class UpdateMinPurchaseTokenAmountUsdEventDTO : UpdateMinPurchaseTokenAmountUsdEventDTOBase { } + + [Event("UpdateMinPurchaseTokenAmountUsd")] + public class UpdateMinPurchaseTokenAmountUsdEventDTOBase : IEventDTO + { + [Parameter("uint256", "minPurchaseTokenAmountUsd", 1, false )] + public virtual BigInteger MinPurchaseTokenAmountUsd { get; set; } + } + + public partial class UpdateSwapOrderEventDTO : UpdateSwapOrderEventDTOBase { } + + [Event("UpdateSwapOrder")] + public class UpdateSwapOrderEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("uint256", "ordexIndex", 2, false )] + public virtual BigInteger OrdexIndex { get; set; } + [Parameter("address[]", "path", 3, false )] + public virtual List Path { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "triggerRatio", 6, false )] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 7, false )] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 8, false )] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class PricePrecisionOutputDTO : PricePrecisionOutputDTOBase { } + + [FunctionOutput] + public class PricePrecisionOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class UsdgPrecisionOutputDTO : UsdgPrecisionOutputDTOBase { } + + [FunctionOutput] + public class UsdgPrecisionOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + + + + + + + + + + + + + public partial class DecreaseOrdersOutputDTO : DecreaseOrdersOutputDTOBase { } + + [FunctionOutput] + public class DecreaseOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "collateralToken", 2)] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 4)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 7)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 8)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 9)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class DecreaseOrdersIndexOutputDTO : DecreaseOrdersIndexOutputDTOBase { } + + [FunctionOutput] + public class DecreaseOrdersIndexOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + + + + + public partial class GetDecreaseOrderOutputDTO : GetDecreaseOrderOutputDTOBase { } + + [FunctionOutput] + public class GetDecreaseOrderOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "collateralToken", 1)] + public virtual string CollateralToken { get; set; } + [Parameter("uint256", "collateralDelta", 2)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("address", "indexToken", 3)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 6)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 7)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 8)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class GetIncreaseOrderOutputDTO : GetIncreaseOrderOutputDTOBase { } + + [FunctionOutput] + public class GetIncreaseOrderOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "purchaseToken", 1)] + public virtual string PurchaseToken { get; set; } + [Parameter("uint256", "purchaseTokenAmount", 2)] + public virtual BigInteger PurchaseTokenAmount { get; set; } + [Parameter("address", "collateralToken", 3)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 4)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 7)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 8)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 9)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class GetSwapOrderOutputDTO : GetSwapOrderOutputDTOBase { } + + [FunctionOutput] + public class GetSwapOrderOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "path0", 1)] + public virtual string Path0 { get; set; } + [Parameter("address", "path1", 2)] + public virtual string Path1 { get; set; } + [Parameter("address", "path2", 3)] + public virtual string Path2 { get; set; } + [Parameter("uint256", "amountIn", 4)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "triggerRatio", 6)] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 7)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 8)] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 9)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class GetUsdgMinPriceOutputDTO : GetUsdgMinPriceOutputDTOBase { } + + [FunctionOutput] + public class GetUsdgMinPriceOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GovOutputDTO : GovOutputDTOBase { } + + [FunctionOutput] + public class GovOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class IncreaseOrdersOutputDTO : IncreaseOrdersOutputDTOBase { } + + [FunctionOutput] + public class IncreaseOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "purchaseToken", 2)] + public virtual string PurchaseToken { get; set; } + [Parameter("uint256", "purchaseTokenAmount", 3)] + public virtual BigInteger PurchaseTokenAmount { get; set; } + [Parameter("address", "collateralToken", 4)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "indexToken", 5)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "sizeDelta", 6)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "triggerPrice", 8)] + public virtual BigInteger TriggerPrice { get; set; } + [Parameter("bool", "triggerAboveThreshold", 9)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("uint256", "executionFee", 10)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class IncreaseOrdersIndexOutputDTO : IncreaseOrdersIndexOutputDTOBase { } + + [FunctionOutput] + public class IncreaseOrdersIndexOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + public partial class IsInitializedOutputDTO : IsInitializedOutputDTOBase { } + + [FunctionOutput] + public class IsInitializedOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + public partial class MinExecutionFeeOutputDTO : MinExecutionFeeOutputDTOBase { } + + [FunctionOutput] + public class MinExecutionFeeOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MinPurchaseTokenAmountUsdOutputDTO : MinPurchaseTokenAmountUsdOutputDTOBase { } + + [FunctionOutput] + public class MinPurchaseTokenAmountUsdOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class RouterOutputDTO : RouterOutputDTOBase { } + + [FunctionOutput] + public class RouterOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + + + + + + + public partial class SwapOrdersOutputDTO : SwapOrdersOutputDTOBase { } + + [FunctionOutput] + public class SwapOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "amountIn", 2)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "triggerRatio", 4)] + public virtual BigInteger TriggerRatio { get; set; } + [Parameter("bool", "triggerAboveThreshold", 5)] + public virtual bool TriggerAboveThreshold { get; set; } + [Parameter("bool", "shouldUnwrap", 6)] + public virtual bool ShouldUnwrap { get; set; } + [Parameter("uint256", "executionFee", 7)] + public virtual BigInteger ExecutionFee { get; set; } + } + + public partial class SwapOrdersIndexOutputDTO : SwapOrdersIndexOutputDTOBase { } + + [FunctionOutput] + public class SwapOrdersIndexOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + + + + + public partial class UsdgOutputDTO : UsdgOutputDTOBase { } + + [FunctionOutput] + public class UsdgOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class ValidatePositionOrderPriceOutputDTO : ValidatePositionOrderPriceOutputDTOBase { } + + [FunctionOutput] + public class ValidatePositionOrderPriceOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + [Parameter("bool", "", 2)] + public virtual bool ReturnValue2 { get; set; } + } + + public partial class ValidateSwapOrderPriceWithTriggerAboveThresholdOutputDTO : ValidateSwapOrderPriceWithTriggerAboveThresholdOutputDTOBase { } + + [FunctionOutput] + public class ValidateSwapOrderPriceWithTriggerAboveThresholdOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + public partial class VaultOutputDTO : VaultOutputDTOBase { } + + [FunctionOutput] + public class VaultOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class WethOutputDTO : WethOutputDTOBase { } + + [FunctionOutput] + public class WethOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } +} diff --git a/src/Managing.Tools.ABI/OrderBook/OrderBookService.cs b/src/Managing.Tools.ABI/OrderBook/OrderBookService.cs new file mode 100644 index 0000000..115af63 --- /dev/null +++ b/src/Managing.Tools.ABI/OrderBook/OrderBookService.cs @@ -0,0 +1,859 @@ +using System.Numerics; +using Nethereum.Web3; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Contracts.ContractHandlers; +using Managing.Tools.OrderBook.ContractDefinition; + +namespace Managing.Tools.OrderBook +{ + public partial class OrderBookService + { + public static Task DeployContractAndWaitForReceiptAsync(Web3 web3, OrderBookDeployment orderBookDeployment, CancellationTokenSource cancellationTokenSource = null) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAndWaitForReceiptAsync(orderBookDeployment, cancellationTokenSource); + } + + public static Task DeployContractAsync(Web3 web3, OrderBookDeployment orderBookDeployment) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAsync(orderBookDeployment); + } + + public static async Task DeployContractAndGetServiceAsync(Web3 web3, OrderBookDeployment orderBookDeployment, CancellationTokenSource cancellationTokenSource = null) + { + var receipt = await DeployContractAndWaitForReceiptAsync(web3, orderBookDeployment, cancellationTokenSource); + return new OrderBookService(web3, receipt.ContractAddress); + } + + protected IWeb3 Web3 { get; } + + public ContractHandler ContractHandler { get; } + + public OrderBookService(Web3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public OrderBookService(IWeb3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public Task PricePrecisionQueryAsync(PricePrecisionFunction pricePrecisionFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(pricePrecisionFunction, blockParameter); + } + + + public Task PricePrecisionQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task UsdgPrecisionQueryAsync(UsdgPrecisionFunction usdgPrecisionFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(usdgPrecisionFunction, blockParameter); + } + + + public Task UsdgPrecisionQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task CancelDecreaseOrderRequestAsync(CancelDecreaseOrderFunction cancelDecreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(cancelDecreaseOrderFunction); + } + + public Task CancelDecreaseOrderRequestAndWaitForReceiptAsync(CancelDecreaseOrderFunction cancelDecreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelDecreaseOrderFunction, cancellationToken); + } + + public Task CancelDecreaseOrderRequestAsync(BigInteger orderIndex) + { + var cancelDecreaseOrderFunction = new CancelDecreaseOrderFunction(); + cancelDecreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAsync(cancelDecreaseOrderFunction); + } + + public Task CancelDecreaseOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, CancellationTokenSource cancellationToken = null) + { + var cancelDecreaseOrderFunction = new CancelDecreaseOrderFunction(); + cancelDecreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelDecreaseOrderFunction, cancellationToken); + } + + public Task CancelIncreaseOrderRequestAsync(CancelIncreaseOrderFunction cancelIncreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(cancelIncreaseOrderFunction); + } + + public Task CancelIncreaseOrderRequestAndWaitForReceiptAsync(CancelIncreaseOrderFunction cancelIncreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelIncreaseOrderFunction, cancellationToken); + } + + public Task CancelIncreaseOrderRequestAsync(BigInteger orderIndex) + { + var cancelIncreaseOrderFunction = new CancelIncreaseOrderFunction(); + cancelIncreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAsync(cancelIncreaseOrderFunction); + } + + public Task CancelIncreaseOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, CancellationTokenSource cancellationToken = null) + { + var cancelIncreaseOrderFunction = new CancelIncreaseOrderFunction(); + cancelIncreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelIncreaseOrderFunction, cancellationToken); + } + + public Task CancelMultipleRequestAsync(CancelMultipleFunction cancelMultipleFunction) + { + return ContractHandler.SendRequestAsync(cancelMultipleFunction); + } + + public Task CancelMultipleRequestAndWaitForReceiptAsync(CancelMultipleFunction cancelMultipleFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelMultipleFunction, cancellationToken); + } + + public Task CancelMultipleRequestAsync(List swapOrderIndexes, List increaseOrderIndexes, List decreaseOrderIndexes) + { + var cancelMultipleFunction = new CancelMultipleFunction(); + cancelMultipleFunction.SwapOrderIndexes = swapOrderIndexes; + cancelMultipleFunction.IncreaseOrderIndexes = increaseOrderIndexes; + cancelMultipleFunction.DecreaseOrderIndexes = decreaseOrderIndexes; + + return ContractHandler.SendRequestAsync(cancelMultipleFunction); + } + + public Task CancelMultipleRequestAndWaitForReceiptAsync(List swapOrderIndexes, List increaseOrderIndexes, List decreaseOrderIndexes, CancellationTokenSource cancellationToken = null) + { + var cancelMultipleFunction = new CancelMultipleFunction(); + cancelMultipleFunction.SwapOrderIndexes = swapOrderIndexes; + cancelMultipleFunction.IncreaseOrderIndexes = increaseOrderIndexes; + cancelMultipleFunction.DecreaseOrderIndexes = decreaseOrderIndexes; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelMultipleFunction, cancellationToken); + } + + public Task CancelSwapOrderRequestAsync(CancelSwapOrderFunction cancelSwapOrderFunction) + { + return ContractHandler.SendRequestAsync(cancelSwapOrderFunction); + } + + public Task CancelSwapOrderRequestAndWaitForReceiptAsync(CancelSwapOrderFunction cancelSwapOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelSwapOrderFunction, cancellationToken); + } + + public Task CancelSwapOrderRequestAsync(BigInteger orderIndex) + { + var cancelSwapOrderFunction = new CancelSwapOrderFunction(); + cancelSwapOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAsync(cancelSwapOrderFunction); + } + + public Task CancelSwapOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, CancellationTokenSource cancellationToken = null) + { + var cancelSwapOrderFunction = new CancelSwapOrderFunction(); + cancelSwapOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelSwapOrderFunction, cancellationToken); + } + + public Task CreateDecreaseOrderRequestAsync(CreateDecreaseOrderFunction createDecreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(createDecreaseOrderFunction); + } + + public Task CreateDecreaseOrderRequestAndWaitForReceiptAsync(CreateDecreaseOrderFunction createDecreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createDecreaseOrderFunction, cancellationToken); + } + + public Task CreateDecreaseOrderRequestAsync(string indexToken, BigInteger sizeDelta, string collateralToken, BigInteger collateralDelta, bool isLong, BigInteger triggerPrice, bool triggerAboveThreshold) + { + var createDecreaseOrderFunction = new CreateDecreaseOrderFunction(); + createDecreaseOrderFunction.IndexToken = indexToken; + createDecreaseOrderFunction.SizeDelta = sizeDelta; + createDecreaseOrderFunction.CollateralToken = collateralToken; + createDecreaseOrderFunction.CollateralDelta = collateralDelta; + createDecreaseOrderFunction.IsLong = isLong; + createDecreaseOrderFunction.TriggerPrice = triggerPrice; + createDecreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAsync(createDecreaseOrderFunction); + } + + public Task CreateDecreaseOrderRequestAndWaitForReceiptAsync(string indexToken, BigInteger sizeDelta, string collateralToken, BigInteger collateralDelta, bool isLong, BigInteger triggerPrice, bool triggerAboveThreshold, CancellationTokenSource cancellationToken = null) + { + var createDecreaseOrderFunction = new CreateDecreaseOrderFunction(); + createDecreaseOrderFunction.IndexToken = indexToken; + createDecreaseOrderFunction.SizeDelta = sizeDelta; + createDecreaseOrderFunction.CollateralToken = collateralToken; + createDecreaseOrderFunction.CollateralDelta = collateralDelta; + createDecreaseOrderFunction.IsLong = isLong; + createDecreaseOrderFunction.TriggerPrice = triggerPrice; + createDecreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createDecreaseOrderFunction, cancellationToken); + } + + public Task CreateIncreaseOrderRequestAsync(CreateIncreaseOrderFunction createIncreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(createIncreaseOrderFunction); + } + + public Task CreateIncreaseOrderRequestAndWaitForReceiptAsync(CreateIncreaseOrderFunction createIncreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreaseOrderFunction, cancellationToken); + } + + public Task CreateIncreaseOrderRequestAsync(List path, BigInteger amountIn, string indexToken, BigInteger minOut, BigInteger sizeDelta, string collateralToken, bool isLong, BigInteger triggerPrice, bool triggerAboveThreshold, BigInteger executionFee, bool shouldWrap) + { + var createIncreaseOrderFunction = new CreateIncreaseOrderFunction(); + createIncreaseOrderFunction.Path = path; + createIncreaseOrderFunction.AmountIn = amountIn; + createIncreaseOrderFunction.IndexToken = indexToken; + createIncreaseOrderFunction.MinOut = minOut; + createIncreaseOrderFunction.SizeDelta = sizeDelta; + createIncreaseOrderFunction.CollateralToken = collateralToken; + createIncreaseOrderFunction.IsLong = isLong; + createIncreaseOrderFunction.TriggerPrice = triggerPrice; + createIncreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + createIncreaseOrderFunction.ExecutionFee = executionFee; + createIncreaseOrderFunction.ShouldWrap = shouldWrap; + + return ContractHandler.SendRequestAsync(createIncreaseOrderFunction); + } + + public Task CreateIncreaseOrderRequestAndWaitForReceiptAsync(List path, BigInteger amountIn, string indexToken, BigInteger minOut, BigInteger sizeDelta, string collateralToken, bool isLong, BigInteger triggerPrice, bool triggerAboveThreshold, BigInteger executionFee, bool shouldWrap, CancellationTokenSource cancellationToken = null) + { + var createIncreaseOrderFunction = new CreateIncreaseOrderFunction(); + createIncreaseOrderFunction.Path = path; + createIncreaseOrderFunction.AmountIn = amountIn; + createIncreaseOrderFunction.IndexToken = indexToken; + createIncreaseOrderFunction.MinOut = minOut; + createIncreaseOrderFunction.SizeDelta = sizeDelta; + createIncreaseOrderFunction.CollateralToken = collateralToken; + createIncreaseOrderFunction.IsLong = isLong; + createIncreaseOrderFunction.TriggerPrice = triggerPrice; + createIncreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + createIncreaseOrderFunction.ExecutionFee = executionFee; + createIncreaseOrderFunction.ShouldWrap = shouldWrap; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreaseOrderFunction, cancellationToken); + } + + public Task CreateSwapOrderRequestAsync(CreateSwapOrderFunction createSwapOrderFunction) + { + return ContractHandler.SendRequestAsync(createSwapOrderFunction); + } + + public Task CreateSwapOrderRequestAndWaitForReceiptAsync(CreateSwapOrderFunction createSwapOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createSwapOrderFunction, cancellationToken); + } + + public Task CreateSwapOrderRequestAsync(List path, BigInteger amountIn, BigInteger minOut, BigInteger triggerRatio, bool triggerAboveThreshold, BigInteger executionFee, bool shouldWrap, bool shouldUnwrap) + { + var createSwapOrderFunction = new CreateSwapOrderFunction(); + createSwapOrderFunction.Path = path; + createSwapOrderFunction.AmountIn = amountIn; + createSwapOrderFunction.MinOut = minOut; + createSwapOrderFunction.TriggerRatio = triggerRatio; + createSwapOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + createSwapOrderFunction.ExecutionFee = executionFee; + createSwapOrderFunction.ShouldWrap = shouldWrap; + createSwapOrderFunction.ShouldUnwrap = shouldUnwrap; + + return ContractHandler.SendRequestAsync(createSwapOrderFunction); + } + + public Task CreateSwapOrderRequestAndWaitForReceiptAsync(List path, BigInteger amountIn, BigInteger minOut, BigInteger triggerRatio, bool triggerAboveThreshold, BigInteger executionFee, bool shouldWrap, bool shouldUnwrap, CancellationTokenSource cancellationToken = null) + { + var createSwapOrderFunction = new CreateSwapOrderFunction(); + createSwapOrderFunction.Path = path; + createSwapOrderFunction.AmountIn = amountIn; + createSwapOrderFunction.MinOut = minOut; + createSwapOrderFunction.TriggerRatio = triggerRatio; + createSwapOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + createSwapOrderFunction.ExecutionFee = executionFee; + createSwapOrderFunction.ShouldWrap = shouldWrap; + createSwapOrderFunction.ShouldUnwrap = shouldUnwrap; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createSwapOrderFunction, cancellationToken); + } + + public Task DecreaseOrdersQueryAsync(DecreaseOrdersFunction decreaseOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(decreaseOrdersFunction, blockParameter); + } + + public Task DecreaseOrdersQueryAsync(string returnValue1, BigInteger returnValue2, BlockParameter blockParameter = null) + { + var decreaseOrdersFunction = new DecreaseOrdersFunction(); + decreaseOrdersFunction.ReturnValue1 = returnValue1; + decreaseOrdersFunction.ReturnValue2 = returnValue2; + + return ContractHandler.QueryDeserializingToObjectAsync(decreaseOrdersFunction, blockParameter); + } + + public Task DecreaseOrdersIndexQueryAsync(DecreaseOrdersIndexFunction decreaseOrdersIndexFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(decreaseOrdersIndexFunction, blockParameter); + } + + + public Task DecreaseOrdersIndexQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var decreaseOrdersIndexFunction = new DecreaseOrdersIndexFunction(); + decreaseOrdersIndexFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(decreaseOrdersIndexFunction, blockParameter); + } + + public Task ExecuteDecreaseOrderRequestAsync(ExecuteDecreaseOrderFunction executeDecreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(executeDecreaseOrderFunction); + } + + public Task ExecuteDecreaseOrderRequestAndWaitForReceiptAsync(ExecuteDecreaseOrderFunction executeDecreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreaseOrderFunction, cancellationToken); + } + + public Task ExecuteDecreaseOrderRequestAsync(string address, BigInteger orderIndex, string feeReceiver) + { + var executeDecreaseOrderFunction = new ExecuteDecreaseOrderFunction(); + executeDecreaseOrderFunction.Address = address; + executeDecreaseOrderFunction.OrderIndex = orderIndex; + executeDecreaseOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAsync(executeDecreaseOrderFunction); + } + + public Task ExecuteDecreaseOrderRequestAndWaitForReceiptAsync(string address, BigInteger orderIndex, string feeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeDecreaseOrderFunction = new ExecuteDecreaseOrderFunction(); + executeDecreaseOrderFunction.Address = address; + executeDecreaseOrderFunction.OrderIndex = orderIndex; + executeDecreaseOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreaseOrderFunction, cancellationToken); + } + + public Task ExecuteIncreaseOrderRequestAsync(ExecuteIncreaseOrderFunction executeIncreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(executeIncreaseOrderFunction); + } + + public Task ExecuteIncreaseOrderRequestAndWaitForReceiptAsync(ExecuteIncreaseOrderFunction executeIncreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreaseOrderFunction, cancellationToken); + } + + public Task ExecuteIncreaseOrderRequestAsync(string address, BigInteger orderIndex, string feeReceiver) + { + var executeIncreaseOrderFunction = new ExecuteIncreaseOrderFunction(); + executeIncreaseOrderFunction.Address = address; + executeIncreaseOrderFunction.OrderIndex = orderIndex; + executeIncreaseOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAsync(executeIncreaseOrderFunction); + } + + public Task ExecuteIncreaseOrderRequestAndWaitForReceiptAsync(string address, BigInteger orderIndex, string feeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeIncreaseOrderFunction = new ExecuteIncreaseOrderFunction(); + executeIncreaseOrderFunction.Address = address; + executeIncreaseOrderFunction.OrderIndex = orderIndex; + executeIncreaseOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreaseOrderFunction, cancellationToken); + } + + public Task ExecuteSwapOrderRequestAsync(ExecuteSwapOrderFunction executeSwapOrderFunction) + { + return ContractHandler.SendRequestAsync(executeSwapOrderFunction); + } + + public Task ExecuteSwapOrderRequestAndWaitForReceiptAsync(ExecuteSwapOrderFunction executeSwapOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeSwapOrderFunction, cancellationToken); + } + + public Task ExecuteSwapOrderRequestAsync(string account, BigInteger orderIndex, string feeReceiver) + { + var executeSwapOrderFunction = new ExecuteSwapOrderFunction(); + executeSwapOrderFunction.Account = account; + executeSwapOrderFunction.OrderIndex = orderIndex; + executeSwapOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAsync(executeSwapOrderFunction); + } + + public Task ExecuteSwapOrderRequestAndWaitForReceiptAsync(string account, BigInteger orderIndex, string feeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeSwapOrderFunction = new ExecuteSwapOrderFunction(); + executeSwapOrderFunction.Account = account; + executeSwapOrderFunction.OrderIndex = orderIndex; + executeSwapOrderFunction.FeeReceiver = feeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeSwapOrderFunction, cancellationToken); + } + + public Task GetDecreaseOrderQueryAsync(GetDecreaseOrderFunction getDecreaseOrderFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getDecreaseOrderFunction, blockParameter); + } + + public Task GetDecreaseOrderQueryAsync(string account, BigInteger orderIndex, BlockParameter blockParameter = null) + { + var getDecreaseOrderFunction = new GetDecreaseOrderFunction(); + getDecreaseOrderFunction.Account = account; + getDecreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.QueryDeserializingToObjectAsync(getDecreaseOrderFunction, blockParameter); + } + + public Task GetIncreaseOrderQueryAsync(GetIncreaseOrderFunction getIncreaseOrderFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getIncreaseOrderFunction, blockParameter); + } + + public Task GetIncreaseOrderQueryAsync(string account, BigInteger orderIndex, BlockParameter blockParameter = null) + { + var getIncreaseOrderFunction = new GetIncreaseOrderFunction(); + getIncreaseOrderFunction.Account = account; + getIncreaseOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.QueryDeserializingToObjectAsync(getIncreaseOrderFunction, blockParameter); + } + + public Task GetSwapOrderQueryAsync(GetSwapOrderFunction getSwapOrderFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getSwapOrderFunction, blockParameter); + } + + public Task GetSwapOrderQueryAsync(string account, BigInteger orderIndex, BlockParameter blockParameter = null) + { + var getSwapOrderFunction = new GetSwapOrderFunction(); + getSwapOrderFunction.Account = account; + getSwapOrderFunction.OrderIndex = orderIndex; + + return ContractHandler.QueryDeserializingToObjectAsync(getSwapOrderFunction, blockParameter); + } + + public Task GetUsdgMinPriceQueryAsync(GetUsdgMinPriceFunction getUsdgMinPriceFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(getUsdgMinPriceFunction, blockParameter); + } + + + public Task GetUsdgMinPriceQueryAsync(string otherToken, BlockParameter blockParameter = null) + { + var getUsdgMinPriceFunction = new GetUsdgMinPriceFunction(); + getUsdgMinPriceFunction.OtherToken = otherToken; + + return ContractHandler.QueryAsync(getUsdgMinPriceFunction, blockParameter); + } + + public Task GovQueryAsync(GovFunction govFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(govFunction, blockParameter); + } + + + public Task GovQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IncreaseOrdersQueryAsync(IncreaseOrdersFunction increaseOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(increaseOrdersFunction, blockParameter); + } + + public Task IncreaseOrdersQueryAsync(string returnValue1, BigInteger returnValue2, BlockParameter blockParameter = null) + { + var increaseOrdersFunction = new IncreaseOrdersFunction(); + increaseOrdersFunction.ReturnValue1 = returnValue1; + increaseOrdersFunction.ReturnValue2 = returnValue2; + + return ContractHandler.QueryDeserializingToObjectAsync(increaseOrdersFunction, blockParameter); + } + + public Task IncreaseOrdersIndexQueryAsync(IncreaseOrdersIndexFunction increaseOrdersIndexFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(increaseOrdersIndexFunction, blockParameter); + } + + + public Task IncreaseOrdersIndexQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var increaseOrdersIndexFunction = new IncreaseOrdersIndexFunction(); + increaseOrdersIndexFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(increaseOrdersIndexFunction, blockParameter); + } + + public Task InitializeRequestAsync(InitializeFunction initializeFunction) + { + return ContractHandler.SendRequestAsync(initializeFunction); + } + + public Task InitializeRequestAndWaitForReceiptAsync(InitializeFunction initializeFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(initializeFunction, cancellationToken); + } + + public Task InitializeRequestAsync(string router, string vault, string weth, string usdg, BigInteger minExecutionFee, BigInteger minPurchaseTokenAmountUsd) + { + var initializeFunction = new InitializeFunction(); + initializeFunction.Router = router; + initializeFunction.Vault = vault; + initializeFunction.Weth = weth; + initializeFunction.Usdg = usdg; + initializeFunction.MinExecutionFee = minExecutionFee; + initializeFunction.MinPurchaseTokenAmountUsd = minPurchaseTokenAmountUsd; + + return ContractHandler.SendRequestAsync(initializeFunction); + } + + public Task InitializeRequestAndWaitForReceiptAsync(string router, string vault, string weth, string usdg, BigInteger minExecutionFee, BigInteger minPurchaseTokenAmountUsd, CancellationTokenSource cancellationToken = null) + { + var initializeFunction = new InitializeFunction(); + initializeFunction.Router = router; + initializeFunction.Vault = vault; + initializeFunction.Weth = weth; + initializeFunction.Usdg = usdg; + initializeFunction.MinExecutionFee = minExecutionFee; + initializeFunction.MinPurchaseTokenAmountUsd = minPurchaseTokenAmountUsd; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(initializeFunction, cancellationToken); + } + + public Task IsInitializedQueryAsync(IsInitializedFunction isInitializedFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(isInitializedFunction, blockParameter); + } + + + public Task IsInitializedQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task MinExecutionFeeQueryAsync(MinExecutionFeeFunction minExecutionFeeFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(minExecutionFeeFunction, blockParameter); + } + + + public Task MinExecutionFeeQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task MinPurchaseTokenAmountUsdQueryAsync(MinPurchaseTokenAmountUsdFunction minPurchaseTokenAmountUsdFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(minPurchaseTokenAmountUsdFunction, blockParameter); + } + + + public Task MinPurchaseTokenAmountUsdQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task RouterQueryAsync(RouterFunction routerFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(routerFunction, blockParameter); + } + + + public Task RouterQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task SetGovRequestAsync(SetGovFunction setGovFunction) + { + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(SetGovFunction setGovFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetGovRequestAsync(string gov) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(string gov, CancellationTokenSource cancellationToken = null) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetMinExecutionFeeRequestAsync(SetMinExecutionFeeFunction setMinExecutionFeeFunction) + { + return ContractHandler.SendRequestAsync(setMinExecutionFeeFunction); + } + + public Task SetMinExecutionFeeRequestAndWaitForReceiptAsync(SetMinExecutionFeeFunction setMinExecutionFeeFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinExecutionFeeFunction, cancellationToken); + } + + public Task SetMinExecutionFeeRequestAsync(BigInteger minExecutionFee) + { + var setMinExecutionFeeFunction = new SetMinExecutionFeeFunction(); + setMinExecutionFeeFunction.MinExecutionFee = minExecutionFee; + + return ContractHandler.SendRequestAsync(setMinExecutionFeeFunction); + } + + public Task SetMinExecutionFeeRequestAndWaitForReceiptAsync(BigInteger minExecutionFee, CancellationTokenSource cancellationToken = null) + { + var setMinExecutionFeeFunction = new SetMinExecutionFeeFunction(); + setMinExecutionFeeFunction.MinExecutionFee = minExecutionFee; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinExecutionFeeFunction, cancellationToken); + } + + public Task SetMinPurchaseTokenAmountUsdRequestAsync(SetMinPurchaseTokenAmountUsdFunction setMinPurchaseTokenAmountUsdFunction) + { + return ContractHandler.SendRequestAsync(setMinPurchaseTokenAmountUsdFunction); + } + + public Task SetMinPurchaseTokenAmountUsdRequestAndWaitForReceiptAsync(SetMinPurchaseTokenAmountUsdFunction setMinPurchaseTokenAmountUsdFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinPurchaseTokenAmountUsdFunction, cancellationToken); + } + + public Task SetMinPurchaseTokenAmountUsdRequestAsync(BigInteger minPurchaseTokenAmountUsd) + { + var setMinPurchaseTokenAmountUsdFunction = new SetMinPurchaseTokenAmountUsdFunction(); + setMinPurchaseTokenAmountUsdFunction.MinPurchaseTokenAmountUsd = minPurchaseTokenAmountUsd; + + return ContractHandler.SendRequestAsync(setMinPurchaseTokenAmountUsdFunction); + } + + public Task SetMinPurchaseTokenAmountUsdRequestAndWaitForReceiptAsync(BigInteger minPurchaseTokenAmountUsd, CancellationTokenSource cancellationToken = null) + { + var setMinPurchaseTokenAmountUsdFunction = new SetMinPurchaseTokenAmountUsdFunction(); + setMinPurchaseTokenAmountUsdFunction.MinPurchaseTokenAmountUsd = minPurchaseTokenAmountUsd; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinPurchaseTokenAmountUsdFunction, cancellationToken); + } + + public Task SwapOrdersQueryAsync(SwapOrdersFunction swapOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(swapOrdersFunction, blockParameter); + } + + public Task SwapOrdersQueryAsync(string returnValue1, BigInteger returnValue2, BlockParameter blockParameter = null) + { + var swapOrdersFunction = new SwapOrdersFunction(); + swapOrdersFunction.ReturnValue1 = returnValue1; + swapOrdersFunction.ReturnValue2 = returnValue2; + + return ContractHandler.QueryDeserializingToObjectAsync(swapOrdersFunction, blockParameter); + } + + public Task SwapOrdersIndexQueryAsync(SwapOrdersIndexFunction swapOrdersIndexFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(swapOrdersIndexFunction, blockParameter); + } + + + public Task SwapOrdersIndexQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var swapOrdersIndexFunction = new SwapOrdersIndexFunction(); + swapOrdersIndexFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(swapOrdersIndexFunction, blockParameter); + } + + public Task UpdateDecreaseOrderRequestAsync(UpdateDecreaseOrderFunction updateDecreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(updateDecreaseOrderFunction); + } + + public Task UpdateDecreaseOrderRequestAndWaitForReceiptAsync(UpdateDecreaseOrderFunction updateDecreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateDecreaseOrderFunction, cancellationToken); + } + + public Task UpdateDecreaseOrderRequestAsync(BigInteger orderIndex, BigInteger collateralDelta, BigInteger sizeDelta, BigInteger triggerPrice, bool triggerAboveThreshold) + { + var updateDecreaseOrderFunction = new UpdateDecreaseOrderFunction(); + updateDecreaseOrderFunction.OrderIndex = orderIndex; + updateDecreaseOrderFunction.CollateralDelta = collateralDelta; + updateDecreaseOrderFunction.SizeDelta = sizeDelta; + updateDecreaseOrderFunction.TriggerPrice = triggerPrice; + updateDecreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAsync(updateDecreaseOrderFunction); + } + + public Task UpdateDecreaseOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, BigInteger collateralDelta, BigInteger sizeDelta, BigInteger triggerPrice, bool triggerAboveThreshold, CancellationTokenSource cancellationToken = null) + { + var updateDecreaseOrderFunction = new UpdateDecreaseOrderFunction(); + updateDecreaseOrderFunction.OrderIndex = orderIndex; + updateDecreaseOrderFunction.CollateralDelta = collateralDelta; + updateDecreaseOrderFunction.SizeDelta = sizeDelta; + updateDecreaseOrderFunction.TriggerPrice = triggerPrice; + updateDecreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateDecreaseOrderFunction, cancellationToken); + } + + public Task UpdateIncreaseOrderRequestAsync(UpdateIncreaseOrderFunction updateIncreaseOrderFunction) + { + return ContractHandler.SendRequestAsync(updateIncreaseOrderFunction); + } + + public Task UpdateIncreaseOrderRequestAndWaitForReceiptAsync(UpdateIncreaseOrderFunction updateIncreaseOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateIncreaseOrderFunction, cancellationToken); + } + + public Task UpdateIncreaseOrderRequestAsync(BigInteger orderIndex, BigInteger sizeDelta, BigInteger triggerPrice, bool triggerAboveThreshold) + { + var updateIncreaseOrderFunction = new UpdateIncreaseOrderFunction(); + updateIncreaseOrderFunction.OrderIndex = orderIndex; + updateIncreaseOrderFunction.SizeDelta = sizeDelta; + updateIncreaseOrderFunction.TriggerPrice = triggerPrice; + updateIncreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAsync(updateIncreaseOrderFunction); + } + + public Task UpdateIncreaseOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, BigInteger sizeDelta, BigInteger triggerPrice, bool triggerAboveThreshold, CancellationTokenSource cancellationToken = null) + { + var updateIncreaseOrderFunction = new UpdateIncreaseOrderFunction(); + updateIncreaseOrderFunction.OrderIndex = orderIndex; + updateIncreaseOrderFunction.SizeDelta = sizeDelta; + updateIncreaseOrderFunction.TriggerPrice = triggerPrice; + updateIncreaseOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateIncreaseOrderFunction, cancellationToken); + } + + public Task UpdateSwapOrderRequestAsync(UpdateSwapOrderFunction updateSwapOrderFunction) + { + return ContractHandler.SendRequestAsync(updateSwapOrderFunction); + } + + public Task UpdateSwapOrderRequestAndWaitForReceiptAsync(UpdateSwapOrderFunction updateSwapOrderFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateSwapOrderFunction, cancellationToken); + } + + public Task UpdateSwapOrderRequestAsync(BigInteger orderIndex, BigInteger minOut, BigInteger triggerRatio, bool triggerAboveThreshold) + { + var updateSwapOrderFunction = new UpdateSwapOrderFunction(); + updateSwapOrderFunction.OrderIndex = orderIndex; + updateSwapOrderFunction.MinOut = minOut; + updateSwapOrderFunction.TriggerRatio = triggerRatio; + updateSwapOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAsync(updateSwapOrderFunction); + } + + public Task UpdateSwapOrderRequestAndWaitForReceiptAsync(BigInteger orderIndex, BigInteger minOut, BigInteger triggerRatio, bool triggerAboveThreshold, CancellationTokenSource cancellationToken = null) + { + var updateSwapOrderFunction = new UpdateSwapOrderFunction(); + updateSwapOrderFunction.OrderIndex = orderIndex; + updateSwapOrderFunction.MinOut = minOut; + updateSwapOrderFunction.TriggerRatio = triggerRatio; + updateSwapOrderFunction.TriggerAboveThreshold = triggerAboveThreshold; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(updateSwapOrderFunction, cancellationToken); + } + + public Task UsdgQueryAsync(UsdgFunction usdgFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(usdgFunction, blockParameter); + } + + + public Task UsdgQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task ValidatePositionOrderPriceQueryAsync(ValidatePositionOrderPriceFunction validatePositionOrderPriceFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(validatePositionOrderPriceFunction, blockParameter); + } + + public Task ValidatePositionOrderPriceQueryAsync(bool triggerAboveThreshold, BigInteger triggerPrice, string indexToken, bool maximizePrice, bool raise, BlockParameter blockParameter = null) + { + var validatePositionOrderPriceFunction = new ValidatePositionOrderPriceFunction(); + validatePositionOrderPriceFunction.TriggerAboveThreshold = triggerAboveThreshold; + validatePositionOrderPriceFunction.TriggerPrice = triggerPrice; + validatePositionOrderPriceFunction.IndexToken = indexToken; + validatePositionOrderPriceFunction.MaximizePrice = maximizePrice; + validatePositionOrderPriceFunction.Raise = raise; + + return ContractHandler.QueryDeserializingToObjectAsync(validatePositionOrderPriceFunction, blockParameter); + } + + public Task ValidateSwapOrderPriceWithTriggerAboveThresholdQueryAsync(ValidateSwapOrderPriceWithTriggerAboveThresholdFunction validateSwapOrderPriceWithTriggerAboveThresholdFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(validateSwapOrderPriceWithTriggerAboveThresholdFunction, blockParameter); + } + + + public Task ValidateSwapOrderPriceWithTriggerAboveThresholdQueryAsync(List path, BigInteger triggerRatio, BlockParameter blockParameter = null) + { + var validateSwapOrderPriceWithTriggerAboveThresholdFunction = new ValidateSwapOrderPriceWithTriggerAboveThresholdFunction(); + validateSwapOrderPriceWithTriggerAboveThresholdFunction.Path = path; + validateSwapOrderPriceWithTriggerAboveThresholdFunction.TriggerRatio = triggerRatio; + + return ContractHandler.QueryAsync(validateSwapOrderPriceWithTriggerAboveThresholdFunction, blockParameter); + } + + public Task VaultQueryAsync(VaultFunction vaultFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(vaultFunction, blockParameter); + } + + + public Task VaultQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task WethQueryAsync(WethFunction wethFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(wethFunction, blockParameter); + } + + + public Task WethQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + } +} diff --git a/src/Managing.Tools.ABI/OrderBookReader/ContractDefinition/OrderBookReaderDefinition.cs b/src/Managing.Tools.ABI/OrderBookReader/ContractDefinition/OrderBookReaderDefinition.cs new file mode 100644 index 0000000..f2507d9 --- /dev/null +++ b/src/Managing.Tools.ABI/OrderBookReader/ContractDefinition/OrderBookReaderDefinition.cs @@ -0,0 +1,94 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; + +namespace Managing.Tools.OrderBookReader.ContractDefinition +{ + + + public partial class OrderBookReaderDeployment : OrderBookReaderDeploymentBase + { + public OrderBookReaderDeployment() : base(BYTECODE) { } + public OrderBookReaderDeployment(string byteCode) : base(byteCode) { } + } + + public class OrderBookReaderDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "608060405234801561001057600080fd5b50610e27806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80630ce933b9146100465780632e18146914610199578063c38ccd5014610253575b600080fd5b6101006004803603606081101561005c57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561008f57600080fd5b8201836020820111156100a157600080fd5b803590602001918460208302840111600160201b831117156100c257600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061030d945050505050565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561014457818101518382015260200161012c565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561018357818101518382015260200161016b565b5050505090500194505050505060405180910390f35b610100600480360360608110156101af57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b8111156101e257600080fd5b8201836020820111156101f457600080fd5b803590602001918460208302840111600160201b8311171561021557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610675945050505050565b6101006004803603606081101561026957600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561029c57600080fd5b8201836020820111156102ae57600080fd5b803590602001918460208302840111600160201b831117156102cf57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610a17945050505050565b606080610318610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160028152509050606084518260600151026001600160401b038111801561036d57600080fd5b50604051908082528060200260200182016040528015610397578160200160208202803683370190505b509050606085518360800151026001600160401b03811180156103b957600080fd5b506040519080825280602002602001820160405280156103e3578160200160208202803683370190505b509050875b865184511015610667578684600001518151811061040257fe5b60200260200101518460200181815250506000806000806000806000876001600160a01b031663026032ee8c604001518d602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101006040518083038186803b15801561047c57600080fd5b505afa158015610490573d6000803e3d6000fd5b505050506040513d6101008110156104a757600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190505050509650965096509650965096509650858a8c606001518d60000151028151811061052457fe5b602002602001018181525050838a8c606001518d60000151026001018151811061054a57fe5b60200260200101818152505082610562576000610565565b60015b60ff168a8c606001518d60000151026002018151811061058157fe5b602002602001018181525050818a8c606001518d6000015102600301815181106105a757fe5b602002602001018181525050806105bf5760006105c2565b60015b60ff168a8c606001518d6000015102600401815181106105de57fe5b60200260200101818152505086898c608001518d60000151028151811061060157fe5b60200260200101906001600160a01b031690816001600160a01b03168152505084898c608001518d60000151026001018151811061063b57fe5b6001600160a01b0390921660209283029190910190910152505088516001018952506103e89350505050565b509097909650945050505050565b606080610680610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160038152509050606084518260600151026001600160401b03811180156106d557600080fd5b506040519080825280602002602001820160405280156106ff578160200160208202803683370190505b509050606085518360800151026001600160401b038111801561072157600080fd5b5060405190808252806020026020018201604052801561074b578160200160208202803683370190505b509050875b865184511015610667578684600001518151811061076a57fe5b6020026020010151846020018181525050600080600080600080600080886001600160a01b031663d0d40cd68d604001518e602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101206040518083038186803b1580156107e557600080fd5b505afa1580156107f9573d6000803e3d6000fd5b505050506040513d61012081101561081057600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291905050505097509750975097509750975097509750848b8d606001518e60000151028151811061089957fe5b602002602001018181525050838b8d606001518e6000015102600101815181106108bf57fe5b602002602001018181525050828b8d606001518e6000015102600201815181106108e557fe5b602002602001018181525050816108fd576000610900565b60015b60ff168b8d606001518e60000151026003018151811061091c57fe5b60200260200101818152505080610934576000610937565b60015b60ff168b8d606001518e60000151026004018151811061095357fe5b602002602001018181525050878a8d608001518e60000151028151811061097657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050868a8d608001518e6000015102600101815181106109b057fe5b60200260200101906001600160a01b031690816001600160a01b031681525050858a8d608001518e6000015102600201815181106109ea57fe5b6001600160a01b0390921660209283029190910190910152505089516001018a5250610750945050505050565b606080610a22610db9565b6040518060a001604052806000815260200160008152602001866001600160a01b031681526020016005815260200160038152509050606084518260600151026001600160401b0381118015610a7757600080fd5b50604051908082528060200260200182016040528015610aa1578160200160208202803683370190505b509050606085518360800151026001600160401b0381118015610ac357600080fd5b50604051908082528060200260200182016040528015610aed578160200160208202803683370190505b509050875b8651845110156106675786846000015181518110610b0c57fe5b6020026020010151846020018181525050600080600080600080600080886001600160a01b031663d3bab1d18d604001518e602001516040518363ffffffff1660e01b815260040180836001600160a01b03168152602001828152602001925050506101206040518083038186803b158015610b8757600080fd5b505afa158015610b9b573d6000803e3d6000fd5b505050506040513d610120811015610bb257600080fd5b810190808051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291908051906020019092919080519060200190929190805190602001909291905050505097509750975097509750975097509750868b8d606001518e600001510281518110610c3b57fe5b602002602001018181525050838b8d606001518e600001510260010181518110610c6157fe5b60200260200101818152505082610c79576000610c7c565b60015b60ff168b8d606001518e600001510260020181518110610c9857fe5b602002602001018181525050818b8d606001518e600001510260030181518110610cbe57fe5b60200260200101818152505080610cd6576000610cd9565b60015b60ff168b8d606001518e600001510260040181518110610cf557fe5b602002602001018181525050878a8d608001518e600001510281518110610d1857fe5b60200260200101906001600160a01b031690816001600160a01b031681525050858a8d608001518e600001510260010181518110610d5257fe5b60200260200101906001600160a01b031690816001600160a01b031681525050848a8d608001518e600001510260020181518110610d8c57fe5b6001600160a01b0390921660209283029190910190910152505089516001018a5250610af2945050505050565b6040518060a00160405280600081526020016000815260200160006001600160a01b031681526020016000815260200160008152509056fea26469706673582212200490431a3ff46032daa47d7d2efe24198ccd39e8e57ff9d6fccef3b97ebe51b664736f6c634300060c0033"; + public OrderBookReaderDeploymentBase() : base(BYTECODE) { } + public OrderBookReaderDeploymentBase(string byteCode) : base(byteCode) { } + + } + + public partial class GetDecreaseOrdersFunction : GetDecreaseOrdersFunctionBase { } + + [Function("getDecreaseOrders", typeof(GetDecreaseOrdersOutputDTO))] + public class GetDecreaseOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "_orderBookAddress", 1)] + public virtual string OrderBookAddress { get; set; } + [Parameter("address", "_account", 2)] + public virtual string Account { get; set; } + [Parameter("uint256[]", "_indices", 3)] + public virtual List Indices { get; set; } + } + + public partial class GetIncreaseOrdersFunction : GetIncreaseOrdersFunctionBase { } + + [Function("getIncreaseOrders", typeof(GetIncreaseOrdersOutputDTO))] + public class GetIncreaseOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "_orderBookAddress", 1)] + public virtual string OrderBookAddress { get; set; } + [Parameter("address", "_account", 2)] + public virtual string Account { get; set; } + [Parameter("uint256[]", "_indices", 3)] + public virtual List Indices { get; set; } + } + + public partial class GetSwapOrdersFunction : GetSwapOrdersFunctionBase { } + + [Function("getSwapOrders", typeof(GetSwapOrdersOutputDTO))] + public class GetSwapOrdersFunctionBase : FunctionMessage + { + [Parameter("address", "_orderBookAddress", 1)] + public virtual string OrderBookAddress { get; set; } + [Parameter("address", "_account", 2)] + public virtual string Account { get; set; } + [Parameter("uint256[]", "_indices", 3)] + public virtual List Indices { get; set; } + } + + public partial class GetDecreaseOrdersOutputDTO : GetDecreaseOrdersOutputDTOBase { } + + [FunctionOutput] + public class GetDecreaseOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + [Parameter("address[]", "", 2)] + public virtual List ReturnValue2 { get; set; } + } + + public partial class GetIncreaseOrdersOutputDTO : GetIncreaseOrdersOutputDTOBase { } + + [FunctionOutput] + public class GetIncreaseOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + [Parameter("address[]", "", 2)] + public virtual List ReturnValue2 { get; set; } + } + + public partial class GetSwapOrdersOutputDTO : GetSwapOrdersOutputDTOBase { } + + [FunctionOutput] + public class GetSwapOrdersOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + [Parameter("address[]", "", 2)] + public virtual List ReturnValue2 { get; set; } + } +} diff --git a/src/Managing.Tools.ABI/OrderBookReader/OrderBookReaderService.cs b/src/Managing.Tools.ABI/OrderBookReader/OrderBookReaderService.cs new file mode 100644 index 0000000..f206d7f --- /dev/null +++ b/src/Managing.Tools.ABI/OrderBookReader/OrderBookReaderService.cs @@ -0,0 +1,88 @@ +using System.Numerics; +using Nethereum.Web3; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Contracts.ContractHandlers; +using Managing.Tools.OrderBookReader.ContractDefinition; + +namespace Managing.Tools.OrderBookReader +{ + public partial class OrderBookReaderService + { + public static Task DeployContractAndWaitForReceiptAsync(Web3 web3, OrderBookReaderDeployment orderBookReaderDeployment, CancellationTokenSource cancellationTokenSource = null) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAndWaitForReceiptAsync(orderBookReaderDeployment, cancellationTokenSource); + } + + public static Task DeployContractAsync(Web3 web3, OrderBookReaderDeployment orderBookReaderDeployment) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAsync(orderBookReaderDeployment); + } + + public static async Task DeployContractAndGetServiceAsync(Web3 web3, OrderBookReaderDeployment orderBookReaderDeployment, CancellationTokenSource cancellationTokenSource = null) + { + var receipt = await DeployContractAndWaitForReceiptAsync(web3, orderBookReaderDeployment, cancellationTokenSource); + return new OrderBookReaderService(web3, receipt.ContractAddress); + } + + protected IWeb3 Web3 { get; } + + public ContractHandler ContractHandler { get; } + + public OrderBookReaderService(Web3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public OrderBookReaderService(IWeb3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public Task GetDecreaseOrdersQueryAsync(GetDecreaseOrdersFunction getDecreaseOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getDecreaseOrdersFunction, blockParameter); + } + + public Task GetDecreaseOrdersQueryAsync(string orderBookAddress, string account, List indices, BlockParameter blockParameter = null) + { + var getDecreaseOrdersFunction = new GetDecreaseOrdersFunction(); + getDecreaseOrdersFunction.OrderBookAddress = orderBookAddress; + getDecreaseOrdersFunction.Account = account; + getDecreaseOrdersFunction.Indices = indices; + + return ContractHandler.QueryDeserializingToObjectAsync(getDecreaseOrdersFunction, blockParameter); + } + + public Task GetIncreaseOrdersQueryAsync(GetIncreaseOrdersFunction getIncreaseOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getIncreaseOrdersFunction, blockParameter); + } + + public Task GetIncreaseOrdersQueryAsync(string orderBookAddress, string account, List indices, BlockParameter blockParameter = null) + { + var getIncreaseOrdersFunction = new GetIncreaseOrdersFunction(); + getIncreaseOrdersFunction.OrderBookAddress = orderBookAddress; + getIncreaseOrdersFunction.Account = account; + getIncreaseOrdersFunction.Indices = indices; + + return ContractHandler.QueryDeserializingToObjectAsync(getIncreaseOrdersFunction, blockParameter); + } + + public Task GetSwapOrdersQueryAsync(GetSwapOrdersFunction getSwapOrdersFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getSwapOrdersFunction, blockParameter); + } + + public Task GetSwapOrdersQueryAsync(string orderBookAddress, string account, List indices, BlockParameter blockParameter = null) + { + var getSwapOrdersFunction = new GetSwapOrdersFunction(); + getSwapOrdersFunction.OrderBookAddress = orderBookAddress; + getSwapOrdersFunction.Account = account; + getSwapOrdersFunction.Indices = indices; + + return ContractHandler.QueryDeserializingToObjectAsync(getSwapOrdersFunction, blockParameter); + } + } +} diff --git a/src/Managing.Tools.ABI/PositionRouter/ContractDefinition/PositionRouterDefinition.cs b/src/Managing.Tools.ABI/PositionRouter/ContractDefinition/PositionRouterDefinition.cs new file mode 100644 index 0000000..d7ddf6a --- /dev/null +++ b/src/Managing.Tools.ABI/PositionRouter/ContractDefinition/PositionRouterDefinition.cs @@ -0,0 +1,1353 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; + +namespace Managing.Tools.PositionRouter.ContractDefinition +{ + + + public partial class PositionRouterDeployment : PositionRouterDeploymentBase + { + public PositionRouterDeployment() : base(BYTECODE) { } + public PositionRouterDeployment(string byteCode) : base(byteCode) { } + } + + public class PositionRouterDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "608060405260646008556011805460ff191660011790553480156200002357600080fd5b50604051620060bb380380620060bb833981810160405260c08110156200004957600080fd5b508051602082015160408301516060840151608085015160a090950151600160008181558154336001600160a01b031991821681179093556003805482166001600160a01b03998a16179055600580548216978916979097179096556006805487169588169590951790945560079690965560048054851695909216949094179055600280549092169093179055600d55615fd0908190620000eb90396000f3fe60806040526004361061027e5760003560e01c80626cc35e146102d357806304225954146103045780630d4d003d146103405780631045c74e1461038d578063126082cf146103c057806312d43a51146103d55780631bca8cf0146103ea5780631ce9cb8f146103ff5780631f28510614610432578063225fc9fd146104d4578063233bfe3b1461050d57806324a084df1461053757806324f746971461057057806327b42c0f14610585578063308aa81f146105be5780633422ead1146105ee57806336eba48a146106295780633a2a80c71461065c5780633e72a262146106715780633fc8cef3146106865780634067b1321461069b5780634278555f146106d1578063490ae210146106fb5780635841fcaa146107255780635b88e8c61461073a5780635d5c22e81461081357806360a362e21461088d57806362f8a3fe146108c6578063633451de146108ff57806363ae210314610932578063657bc5d01461094757806367a527931461095c578063704b6c02146109715780637be7d141146109a45780637c2eb9f714610a8f5780638a54942f14610abb57806395e9bbd714610ae55780639698d25a14610b0f57806398d1e03a14610b425780639a20810014610b575780639b57862014610b90578063ae4d7f9a14610ba5578063cb0269c914610bd8578063cfad57a214610bed578063e1f21c6714610c20578063ef12c67e14610c63578063f255527814610e15578063f2ae372f14610e50578063f2cea6a514610f2f578063f3883d8b14610f6a578063f851a44014610fa3578063f887ea4014610fb8578063fa44457714610fcd578063faf990f314611000578063fbfa77cf14611099578063fc2cee62146110ae576102ce565b366102ce576006546001600160a01b031633146102cc5760405162461bcd60e51b8152600401808060200182810382526023815260200180615f4e6023913960400191505060405180910390fd5b005b600080fd5b3480156102df57600080fd5b506102e86110d8565b604080516001600160a01b039092168252519081900360200190f35b34801561031057600080fd5b5061032e6004803603602081101561032757600080fd5b50356110e7565b60408051918252519081900360200190f35b34801561034c57600080fd5b506103796004803603604081101561036357600080fd5b50803590602001356001600160a01b0316611105565b604080519115158252519081900360200190f35b34801561039957600080fd5b5061032e600480360360208110156103b057600080fd5b50356001600160a01b03166115a3565b3480156103cc57600080fd5b5061032e6115b5565b3480156103e157600080fd5b506102e86115bb565b3480156103f657600080fd5b5061032e6115ca565b34801561040b57600080fd5b5061032e6004803603602081101561042257600080fd5b50356001600160a01b03166115d0565b34801561043e57600080fd5b5061045c6004803603602081101561045557600080fd5b50356115e2565b604080516001600160a01b039e8f1681529c8e1660208e01528c81019b909b5260608c019990995296151560808b0152948a1660a08a015260c089019390935260e088019190915261010087015261012086015261014085015215156101608401529092166101808201529051908190036101a00190f35b3480156104e057600080fd5b50610379600480360360408110156104f757600080fd5b50803590602001356001600160a01b0316611655565b34801561051957600080fd5b506102cc6004803603602081101561053057600080fd5b5035611a1f565b34801561054357600080fd5b506102cc6004803603604081101561055a57600080fd5b506001600160a01b038135169060200135611aa7565b34801561057c57600080fd5b5061032e611b0b565b34801561059157600080fd5b50610379600480360360408110156105a857600080fd5b50803590602001356001600160a01b0316611b11565b3480156105ca57600080fd5b506102cc600480360360408110156105e157600080fd5b5080359060200135611f73565b3480156105fa57600080fd5b506102cc6004803603604081101561061157600080fd5b506001600160a01b0381351690602001351515612009565b34801561063557600080fd5b506103796004803603602081101561064c57600080fd5b50356001600160a01b03166120b6565b34801561066857600080fd5b5061032e6120cb565b34801561067d57600080fd5b506103796120d1565b34801561069257600080fd5b506102e86120da565b3480156106a757600080fd5b506102cc600480360360608110156106be57600080fd5b50803590602081013590604001356120e9565b3480156106dd57600080fd5b5061032e600480360360208110156106f457600080fd5b503561218a565b34801561070757600080fd5b506102cc6004803603602081101561071e57600080fd5b5035612197565b34801561073157600080fd5b5061032e61221f565b61032e600480360361012081101561075157600080fd5b810190602081018135600160201b81111561076b57600080fd5b82018360208201111561077d57600080fd5b803590602001918460208302840111600160201b8311171561079e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b03908116935060208301359260408101359250606081013515159160808201359160a08101359160c08201359160e0013516612225565b34801561081f57600080fd5b5061083d6004803603602081101561083657600080fd5b50356123e1565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610879578181015183820152602001610861565b505050509050019250505060405180910390f35b34801561089957600080fd5b50610379600480360360408110156108b057600080fd5b50803590602001356001600160a01b0316612517565b3480156108d257600080fd5b5061032e600480360360408110156108e957600080fd5b506001600160a01b0381351690602001356128b5565b34801561090b57600080fd5b5061032e6004803603602081101561092257600080fd5b50356001600160a01b03166128fb565b34801561093e57600080fd5b5061032e61290d565b34801561095357600080fd5b506102e8612913565b34801561096857600080fd5b5061032e612922565b34801561097d57600080fd5b506102cc6004803603602081101561099457600080fd5b50356001600160a01b0316612928565b61032e60048036036101608110156109bb57600080fd5b810190602081018135600160201b8111156109d557600080fd5b8201836020820111156109e757600080fd5b803590602001918460208302840111600160201b83111715610a0857600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b039081169350602083013592604081013592506060810135151591608082013581169160a08101359160c08201359160e0810135916101008201351515916101200135166129c9565b348015610a9b57600080fd5b506102cc60048036036020811015610ab257600080fd5b50351515612b79565b348015610ac757600080fd5b506102cc60048036036020811015610ade57600080fd5b5035612c0d565b348015610af157600080fd5b5061083d60048036036020811015610b0857600080fd5b5035612c95565b348015610b1b57600080fd5b5061032e60048036036020811015610b3257600080fd5b50356001600160a01b0316612dc3565b348015610b4e57600080fd5b5061032e612dd5565b348015610b6357600080fd5b506102cc60048036036040811015610b7a57600080fd5b50803590602001356001600160a01b0316612ddb565b348015610b9c57600080fd5b5061032e612fb3565b348015610bb157600080fd5b506102cc60048036036020811015610bc857600080fd5b50356001600160a01b0316612fb9565b348015610be457600080fd5b5061032e61305a565b348015610bf957600080fd5b506102cc60048036036020811015610c1057600080fd5b50356001600160a01b0316613060565b348015610c2c57600080fd5b506102cc60048036036060811015610c4357600080fd5b506001600160a01b038135811691602081013590911690604001356130cf565b348015610c6f57600080fd5b506102cc60048036036060811015610c8657600080fd5b810190602081018135600160201b811115610ca057600080fd5b820183602082011115610cb257600080fd5b803590602001918460208302840111600160201b83111715610cd357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610d2257600080fd5b820183602082011115610d3457600080fd5b803590602001918460208302840111600160201b83111715610d5557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610da457600080fd5b820183602082011115610db657600080fd5b803590602001918460208302840111600160201b83111715610dd757600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506131a4945050505050565b348015610e2157600080fd5b506102cc60048036036040811015610e3857600080fd5b506001600160a01b0381358116916020013516613393565b61032e6004803603610140811015610e6757600080fd5b810190602081018135600160201b811115610e8157600080fd5b820183602082011115610e9357600080fd5b803590602001918460208302840111600160201b83111715610eb457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b038335811694506020840135936040810135935060608101359250608081013515159160a08201359160c08101359160e082013591610100013516613477565b348015610f3b57600080fd5b50610f4461365a565b604080519485526020850193909352838301919091526060830152519081900360800190f35b348015610f7657600080fd5b506102cc60048036036040811015610f8d57600080fd5b50803590602001356001600160a01b031661366c565b348015610faf57600080fd5b506102e8613844565b348015610fc457600080fd5b506102e8613853565b348015610fd957600080fd5b5061032e60048036036020811015610ff057600080fd5b50356001600160a01b0316613862565b34801561100c57600080fd5b5061102a6004803603602081101561102357600080fd5b5035613874565b604080516001600160a01b039d8e1681529b8d1660208d01528b81019a909a5260608b019890985260808a019690965293151560a089015260c088019290925260e087015261010086015261012085015215156101408401529092166101608201529051908190036101800190f35b3480156110a557600080fd5b506102e86138e8565b3480156110ba57600080fd5b506102cc600480360360208110156110d157600080fd5b50356138f7565b6009546001600160a01b031681565b601281815481106110f457fe5b600091825260209091200154905081565b60006002600054141561114d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b600260005561115a615b85565b6000848152601b602090815260409182902082516101c08101845281546001600160a01b03168152600182018054855181860281018601909652808652919492938581019392908301828280156111da57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116111bc575b505050918352505060028201546001600160a01b0390811660208301526003830154604083015260048301546060830152600583015460ff8082161515608085015261010091829004831660a0850152600685015460c0850152600785015460e08501526008850154828501526009850154610120850152600a850154610140850152600b90940154938416151561016084015290920482166101809091015281519192501661128e576001915050611598565b60006112a9826101400151836101600151846000015161397f565b9050806112bb57600092505050611598565b6000858152601b6020526040812080546001600160a01b0319168155906112e56001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820180546001600160a81b031990811690915560068301829055600783018290556008830182905560098301829055600a8301829055600b909201805490921690915582516020840151805161138b929190849061136457fe5b60200260200101518560400151866060015187608001518860a00151308a60e00151613b05565b9050801561143a57600183602001515111156113fb57600354602084015180516113e4926001600160a01b03169184916000906113c457fe5b60200260200101516001600160a01b0316613f9e9092919063ffffffff16565b6113f8836020015184610100015130613ff5565b90505b8261018001511561141957611414818460c00151614073565b61143a565b61143a8360c00151828560200151600187602001515103815181106113c457fe5b61144983610120015186614073565b82600001516001600160a01b03167f21435c5b618d77ff3657140cd3318e2cffaebc5e0e1b7318f56a9ba4044c3ed284602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001516114bf8e61014001514361410190919063ffffffff16565b6101608f01516114d0904290614101565b60405180806020018c6001600160a01b031681526020018b81526020018a81526020018915158152602001886001600160a01b0316815260200187815260200186815260200185815260200184815260200183815260200182810382528d818151815260200191508051906020019060200280838360005b83811015611560578181015183820152602001611548565b505050509050019c5050505050505050505050505060405180910390a2611590836101a001518760016000614143565b600193505050505b600160005592915050565b600b6020526000908152604090205481565b61271081565b6001546001600160a01b031681565b60155481565b600a6020526000908152604090205481565b601b602052600090815260409020805460028201546003830154600484015460058501546006860154600787015460088801546009890154600a8a0154600b909a01546001600160a01b03998a169a988a16999798969760ff80881698610100988990048316989093918216929104168d565b60006002600054141561169d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b60026000556116aa615c3a565b60008481526019602090815260409182902082516101a08101845281546001600160a01b031681526001820180548551818602810186019096528086529194929385810193929083018282801561172a57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161170c575b505050918352505060028201546001600160a01b039081166020830152600383015460408301526004830154606083015260058301546080830152600683015460ff908116151560a0840152600784015460c0840152600884015460e0840152600984015461010080850191909152600a850154610120850152600b909401549081161515610140840152929092048216610160909101528151919250166117d6576001915050611598565b60006117f18261012001518361014001518460000151614249565b90508061180357600092505050611598565b600085815260196020526040812080546001600160a01b03191681559061182d6001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820181905560068201805460ff19169055600782018190556008820181905560098201819055600a820155600b0180546001600160a81b0319169055610160820151156118b0576118ab82606001518360000151614073565b6118ce565b6118ce8260000151836060015184602001516000815181106113c457fe5b6118dd82610100015185614073565b81600001516001600160a01b03167f35b638e650e2328786fb405bd69d2083dbedc018d086662e74b775b4f1dae4bf83602001518460400151856060015186608001518760a001518860c001518960e001518a610100015161194d8c61012001514361410190919063ffffffff16565b6101408d015161195e904290614101565b60405180806020018b6001600160a01b031681526020018a8152602001898152602001888152602001871515815260200186815260200185815260200184815260200183815260200182810382528c818151815260200191508051906020019060200280838360005b838110156119df5781810151838201526020016119c7565b505050509050019b50505050505050505050505060405180910390a2611a0e8261018001518660006001614143565b600192505050600160005592915050565b6002546001600160a01b03163314611a6c576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60088190556040805182815290517f21167d0d4661af93817ebce920f18986eed3d75d5e1c03f2aed05efcbafbc4529181900360200190a150565b6001546001600160a01b03163314611af4576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b611b076001600160a01b038316826142b1565b5050565b60165481565b600060026000541415611b59576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055611b66615c3a565b60008481526019602090815260409182902082516101a08101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015611be657602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611bc8575b505050918352505060028201546001600160a01b039081166020830152600383015460408301526004830154606083015260058301546080830152600683015460ff908116151560a0840152600784015460c0840152600884015460e0840152600984015461010080850191909152600a850154610120850152600b90940154908116151561014084015292909204821661016090910152815191925016611c92576001915050611598565b6000611cad826101200151836101400151846000015161397f565b905080611cbf57600092505050611598565b600085815260196020526040812080546001600160a01b031916815590611ce96001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820181905560068201805460ff19169055600782018190556008820181905560098201819055600a820155600b0180546001600160a81b0319169055606082015115611df657606082015160208301515160011015611da557600354606084015160208501518051611d8f936001600160a01b03169291906000906113c457fe5b611da28360200151846080015130613ff5565b90505b6000611dc53385602001518487604001518860c001518960a00151614396565b60035460208601518051929350611df3926001600160a01b039092169184919060001981019081106113c457fe5b50505b815160208301518051611e349291906000198101908110611e1357fe5b602002602001015184604001518560a001518660c001518760e00151614475565b611e4382610100015185614073565b81600001516001600160a01b03167f1be316b94d38c07bd41cdb4913772d0a0a82802786a2f8b657b6e85dbcdfc64183602001518460400151856060015186608001518760a001518860c001518960e001518a6101000151611eb38c61012001514361410190919063ffffffff16565b6101408d0151611ec4904290614101565b60405180806020018b6001600160a01b031681526020018a8152602001898152602001888152602001871515815260200186815260200185815260200184815260200183815260200182810382528c818151815260200191508051906020019060200280838360005b83811015611f45578181015183820152602001611f2d565b505050509050019b50505050505050505050505060405180910390a2611a0e82610180015186600180614143565b6002546001600160a01b03163314611fc0576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60148290556015819055604080518381526020810183905281517febb0f666150f4be5b60c45df8f3e49992510b0128027fe58eea6110f296493bc929181900390910190a15050565b6002546001600160a01b03163314612056576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6001600160a01b038216600081815260176020908152604091829020805460ff1916851515908117909155825190815291517ffbabc02389290a451c6e600d05bf9887b99bfad39d8e1237e4e3df042e4941fe9281900390910190a25050565b60176020526000908152604090205460ff1681565b600f5481565b60115460ff1681565b6006546001600160a01b031681565b6002546001600160a01b03163314612136576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600e839055600f8290556010819055604080518481526020810184905280820183905290517fb98e759701eaca2e60c25e91109003c1c7442ef731b5d569037063005da8254d9181900360600190a1505050565b601381815481106110f457fe5b6002546001600160a01b031633146121e4576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60078190556040805182815290517f974fd3c1fcb4653dfc4fb740c4c692cd212d55c28f163f310128cb64d83006759181900360200190a150565b600e5481565b60006002600054141561226d576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d548410156122af576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b833410156122ea576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8951600114806122fb575089516002145b612332576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b6006548a516001600160a01b03909116908b9060009061234e57fe5b60200260200101516001600160a01b03161461239a576040805162461bcd60e51b815260206004808301919091526024820152630e0c2e8d60e31b604482015290519081900360640190fd5b6123a261487d565b6123ab836148e9565b60006123b73486614101565b90506123cd338c8c848d8d8d8d8d60018d61495d565b60016000559b9a5050505050505050505050565b60606123eb615b85565b6000838152601b602090815260409182902082516101c08101845281546001600160a01b031681526001820180548551818602810186019096528086529194929385810193929083018282801561246b57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161244d575b505050918352505060028201546001600160a01b039081166020808401919091526003840154604084015260048401546060840152600584015460ff8082161515608086015261010091829004841660a0860152600686015460c0860152600786015460e08601526008860154828601526009860154610120860152600a860154610140860152600b909501549485161515610160850152909304166101809091015201519392505050565b60006002600054141561255f576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b600260005561256c615b85565b6000848152601b602090815260409182902082516101c08101845281546001600160a01b03168152600182018054855181860281018601909652808652919492938581019392908301828280156125ec57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116125ce575b505050918352505060028201546001600160a01b0390811660208301526003830154604083015260048301546060830152600583015460ff8082161515608085015261010091829004831660a0850152600685015460c0850152600785015460e08501526008850154828501526009850154610120850152600a850154610140850152600b9094015493841615156101608401529092048216610180909101528151919250166126a0576001915050611598565b60006126bb8261014001518361016001518460000151614249565b9050806126cd57600092505050611598565b6000858152601b6020526040812080546001600160a01b0319168155906126f76001830182615c1c565b506002810180546001600160a01b0319169055600060038201819055600482018190556005820180546001600160a81b031990811690915560068301829055600783018290556008830182905560098301829055600a830191909155600b9091018054909116905561012082015161276f9085614073565b81600001516001600160a01b03167f87abfd78e844f28318363bdf3da99eab2f4a2da9ff7ae365484507f7b6c3f80583602001518460400151856060015186608001518760a001518860c001518960e001518a61010001518b61012001516127e58d61014001514361410190919063ffffffff16565b6101608e01516127f6904290614101565b60405180806020018c6001600160a01b031681526020018b81526020018a81526020018915158152602001886001600160a01b0316815260200187815260200186815260200185815260200184815260200183815260200182810382528d818151815260200191508051906020019060200280838360005b8381101561288657818101518382015260200161286e565b505050509050019c5050505050505050505050505060405180910390a2611a0e826101a0015186600080614143565b6000828260405160200180836001600160a01b031660601b8152601401828152602001925050506040516020818303038152906040528051906020012090505b92915050565b60186020526000908152604090205481565b600d5481565b6004546001600160a01b031681565b60075481565b6001546001600160a01b03163314612975576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b600280546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f5a272403b402d892977df56625f4164ccaf70ca3863991c43ecfe76a6905b0a19181900360200190a150565b600060026000541415612a11576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d54841015612a53576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b833414612a8d576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8b5160011480612a9e57508b516002145b612ad5576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b8215612b48576006548c516001600160a01b03909116908d906000198101908110612afc57fe5b60200260200101516001600160a01b031614612b48576040805162461bcd60e51b815260206004808301919091526024820152630e0c2e8d60e31b604482015290519081900360640190fd5b612b5061487d565b612b64338d8d8d8d8d8d8d8d8d8d8d614af4565b60016000559c9b505050505050505050505050565b6002546001600160a01b03163314612bc6576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6011805482151560ff19909116811790915560408051918252517f4eb87a5935d402aa24c01b45bfb30adefcd2328b480f2d967864de4b64ea929f9181900360200190a150565b6002546001600160a01b03163314612c5a576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60168190556040805182815290517f22bd2c9f980325d046be74aaef5fc76df4a2bc3fbc7c5a1200fcc79fe80dab6c9181900360200190a150565b6060612c9f615c3a565b60008381526019602090815260409182902082516101a08101845281546001600160a01b0316815260018201805485518186028101860190965280865291949293858101939290830182828015612d1f57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612d01575b505050918352505060028201546001600160a01b03908116602080840191909152600384015460408401526004840154606084015260058401546080840152600684015460ff908116151560a0850152600785015460c0850152600885015460e0850152600985015461010080860191909152600a860154610120860152600b90950154908116151561014085015293909304166101609091015201519392505050565b600c6020526000908152604090205481565b60085481565b3360009081526017602052604090205460ff16612e25576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b601454601254808210612e39575050611b07565b80841115612e45578093505b83821015612fab57600060128381548110612e5c57fe5b90600052602060002001549050306001600160a01b03166327b42c0f82866040518363ffffffff1660e01b815260040180838152602001826001600160a01b0316815260200192505050602060405180830381600087803b158015612ec057600080fd5b505af1925050508015612ee557506040513d6020811015612ee057600080fd5b505160015b612f79576040805163225fc9fd60e01b8152600481018390526001600160a01b03861660248201529051309163225fc9fd9160448083019260209291908290030181600087803b158015612f3857600080fd5b505af1925050508015612f5d57506040513d6020811015612f5857600080fd5b505160015b612f6657612f74565b80612f72575050612fab565b505b612f87565b80612f85575050612fab565b505b60128381548110612f9457fe5b600091825260208220015550600190910190612e45565b506014555050565b60145481565b6002546001600160a01b03163314613006576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600980546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f828abcccea18192c21d645e575652c49e20b986dab777906fc473d056b01b6a89181900360200190a150565b60105481565b6001546001600160a01b031633146130ad576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6001546001600160a01b0316331461311c576040805162461bcd60e51b81526020600482015260156024820152600080516020615da9833981519152604482015290519081900360640190fd5b826001600160a01b031663095ea7b383836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b15801561317357600080fd5b505af1158015613187573d6000803e3d6000fd5b505050506040513d602081101561319d57600080fd5b5050505050565b6002546001600160a01b031633146131f1576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b60005b835181101561328c57600084828151811061320b57fe5b6020026020010151905083828151811061322157fe5b6020026020010151600b6000836001600160a01b03166001600160a01b031681526020019081526020016000208190555082828151811061325e57fe5b6020908102919091018101516001600160a01b039092166000908152600c90915260409020556001016131f4565b507fae32d569b058895b9620d6552b09aaffedc9a6f396be4d595a224ad09f8b213983838360405180806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b838110156132f95781810151838201526020016132e1565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015613338578181015183820152602001613320565b50505050905001848103825285818151815260200191508051906020019060200280838360005b8381101561337757818101518382015260200161335f565b50505050905001965050505050505060405180910390a1505050565b6002546001600160a01b031633146133e0576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b6001600160a01b0382166000908152600a6020526040902054806134045750611b07565b6001600160a01b0383166000818152600a6020526040812055613428908383613f9e565b604080516001600160a01b0380861682528416602082015280820183905290517f4f1b51dd7a2fcb861aa2670f668be66835c4ee12b4bbbf037e4d0018f39819e49181900360600190a1505050565b6000600260005414156134bf576040805162461bcd60e51b815260206004820152601f6024820152600080516020615d89833981519152604482015290519081900360640190fd5b6002600055600d54841015613501576040805162461bcd60e51b815260206004820152600360248201526266656560e81b604482015290519081900360640190fd5b83341461353b576040805162461bcd60e51b81526020600482015260036024820152621d985b60ea1b604482015290519081900360640190fd5b8a516001148061354c57508a516002145b613583576040805162461bcd60e51b81526020600482015260036024820152623632b760e91b604482015290519081900360640190fd5b61358b61487d565b613594836148e9565b8815613646576005548b516001600160a01b0390911690631b827878908d906000906135bc57fe5b602002602001015133308d6040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b03168152602001828152602001945050505050600060405180830381600087803b15801561362d57600080fd5b505af1158015613641573d6000803e3d6000fd5b505050505b6123cd338c8c8c8c8c8c8c8c60008c61495d565b60145460125460155460135490919293565b3360009081526017602052604090205460ff166136b6576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b6015546013548082106136ca575050611b07565b808411156136d6578093505b8382101561383c576000601383815481106136ed57fe5b90600052602060002001549050306001600160a01b0316630d4d003d82866040518363ffffffff1660e01b815260040180838152602001826001600160a01b0316815260200192505050602060405180830381600087803b15801561375157600080fd5b505af192505050801561377657506040513d602081101561377157600080fd5b505160015b61380a5760408051633051b17160e11b8152600481018390526001600160a01b0386166024820152905130916360a362e29160448083019260209291908290030181600087803b1580156137c957600080fd5b505af19250505080156137ee57506040513d60208110156137e957600080fd5b505160015b6137f757613805565b8061380357505061383c565b505b613818565b8061381657505061383c565b505b6013838154811061382557fe5b6000918252602082200155506001909101906136d6565b506015555050565b6002546001600160a01b031681565b6005546001600160a01b031681565b601a6020526000908152604090205481565b6019602052600090815260409020805460028201546003830154600484015460058501546006860154600787015460088801546009890154600a8a0154600b909a01546001600160a01b03998a169a988a169997989697959660ff958616969495939492939092908216916101009004168c565b6003546001600160a01b031681565b6002546001600160a01b03163314613944576040805162461bcd60e51b815260206004820152601e6024820152600080516020615e84833981519152604482015290519081900360640190fd5b600d8190556040805182815290517f52a8358457e20bbb36e4086b83fb0749599f1893fe4c35a876c46dc4886d12db9181900360200190a150565b60004261399760105485614cce90919063ffffffff16565b116139d3576040805162461bcd60e51b8152602060048201526007602482015266195e1c1a5c995960ca1b604482015290519081900360640190fd5b6000333014806139f257503360009081526017602052604090205460ff165b60115490915060ff16158015613a06575080155b15613a3e576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b8015613a645743613a5a600e5487614cce90919063ffffffff16565b1115915050613afe565b336001600160a01b03841614613aa7576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b42613abd600f5486614cce90919063ffffffff16565b1115613af8576040805162461bcd60e51b815260206004820152600560248201526464656c617960d81b604482015290519081900360640190fd5b60019150505b9392505050565b6003546000906001600160a01b03168185613b9857816001600160a01b031663e124e6d28a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613b6757600080fd5b505afa158015613b7b573d6000803e3d6000fd5b505050506040513d6020811015613b9157600080fd5b5051613c12565b816001600160a01b03166381a612d68a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613be557600080fd5b505afa158015613bf9573d6000803e3d6000fd5b505050506040513d6020811015613c0f57600080fd5b50515b90508515613c5e5783811015613c595760405162461bcd60e51b8152600401808060200182810382526030815260200180615e546030913960400191505060405180910390fd5b613c9d565b83811115613c9d5760405162461bcd60e51b8152600401808060200182810382526031815260200180615ef46031913960400191505060405180910390fd5b6000826001600160a01b03166312d43a516040518163ffffffff1660e01b815260040160206040518083038186803b158015613cd857600080fd5b505afa158015613cec573d6000803e3d6000fd5b505050506040513d6020811015613d0257600080fd5b81019080805190602001909291905050509050600460009054906101000a90046001600160a01b03166001600160a01b031663f3238cec8d8d8d8b8d8860006040518863ffffffff1660e01b815260040180886001600160a01b03168152602001876001600160a01b03168152602001866001600160a01b0316815260200185151581526020018481526020018381526020018215158152602001975050505050505050600060405180830381600087803b158015613dc057600080fd5b505af1158015613dd4573d6000803e3d6000fd5b50505050806001600160a01b0316636d63c1d0846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b158015613e2757600080fd5b505af1158015613e3b573d6000803e3d6000fd5b505050506000600560009054906101000a90046001600160a01b03166001600160a01b0316632662166b8e8e8e8e8e8e8e6040518863ffffffff1660e01b815260040180886001600160a01b03168152602001876001600160a01b03168152602001866001600160a01b031681526020018581526020018481526020018315158152602001826001600160a01b03168152602001975050505050505050602060405180830381600087803b158015613ef257600080fd5b505af1158015613f06573d6000803e3d6000fd5b505050506040513d6020811015613f1c57600080fd5b50516040805163d3c87bbb60e01b81526001600160a01b03878116600483015291519293509084169163d3c87bbb9160248082019260009290919082900301818387803b158015613f6c57600080fd5b505af1158015613f80573d6000803e3d6000fd5b50505050613f8e8d8a614d26565b9c9b505050505050505050505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052613ff0908490614eaf565b505050565b600083516002141561403c576140358460008151811061401157fe5b60200260200101518560018151811061402657fe5b60200260200101518585614f60565b9050613afe565b60405162461bcd60e51b8152600401808060200182810382526029815260200180615f256029913960400191505060405180910390fd5b60065460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156140c057600080fd5b505af11580156140d4573d6000803e3d6000fd5b50506040516001600160a01b038416925084156108fc02915084906000818181858888f150505050505050565b6000613afe83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250615036565b6001600160a01b03841661415657614243565b614168846001600160a01b03166150cd565b61417157614243565b6016548061417f5750614243565b6000856001600160a01b031663edf3daec838787876040518563ffffffff1660e01b815260040180848152602001831515815260200182151581526020019350505050600060405180830381600088803b1580156141dc57600080fd5b5087f1935050505080156141ee575060015b6141f7576141fb565b5060015b604080516001600160a01b0388168152821515602082015281517f46ddbd62fc1a7626fe9c43026cb0694aec0b031fe81ac66fb4cfe9381dc6fe72929181900390910190a150505b50505050565b600080333014806139f257503360009081526017602052604090205460ff1660115490915060ff16158015613a06575080613a3e576040805162461bcd60e51b815260206004820152600360248201526234303360e81b604482015290519081900360640190fd5b80471015614306576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114614351576040519150601f19603f3d011682016040523d82523d6000602084013e614356565b606091505b5050905080613ff05760405162461bcd60e51b815260040180806020018281038252603a815260200180615df4603a913960400191505060405180910390fd5b6000806143a78888888888886150d3565b905080156144665760006143de6127106143d86143d160075461271061410190919063ffffffff16565b8a906152b0565b90615309565b905060006143ec8883614101565b905060008960018b51038151811061440057fe5b6020026020010151905061444282600a6000846001600160a01b03166001600160a01b0316815260200190815260200160002054614cce90919063ffffffff16565b6001600160a01b039091166000908152600a602052604090205550915061446b9050565b859150505b9695505050505050565b6003546001600160a01b031660008361450657816001600160a01b03166381a612d6876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156144d557600080fd5b505afa1580156144e9573d6000803e3d6000fd5b505050506040513d60208110156144ff57600080fd5b5051614580565b816001600160a01b031663e124e6d2876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561455357600080fd5b505afa158015614567573d6000803e3d6000fd5b505050506040513d602081101561457d57600080fd5b50515b905083156145cc57828111156145c75760405162461bcd60e51b8152600401808060200182810382526031815260200180615ef46031913960400191505060405180910390fd5b61460b565b8281101561460b5760405162461bcd60e51b8152600401808060200182810382526030815260200180615e546030913960400191505060405180910390fd5b614616868587615348565b6000826001600160a01b03166312d43a516040518163ffffffff1660e01b815260040160206040518083038186803b15801561465157600080fd5b505afa158015614665573d6000803e3d6000fd5b505050506040513d602081101561467b57600080fd5b50516004805460408051633cc8e33b60e21b81526001600160a01b038e8116948201949094528c841660248201528b841660448201528915156064820152608481018b905260a48101879052600160c4820152905193945091169163f3238cec9160e48082019260009290919082900301818387803b1580156146fd57600080fd5b505af1158015614711573d6000803e3d6000fd5b50505050806001600160a01b0316636d63c1d0846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b15801561476457600080fd5b505af1158015614778573d6000803e3d6000fd5b505060055460408051630f8ee8bb60e11b81526001600160a01b038e811660048301528d811660248301528c81166044830152606482018c90528a151560848301529151919092169350631f1dd176925060a480830192600092919082900301818387803b1580156147e957600080fd5b505af11580156147fd573d6000803e3d6000fd5b50505050806001600160a01b031663d3c87bbb846040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b15801561485057600080fd5b505af1158015614864573d6000803e3d6000fd5b5050505061487289876154fa565b505050505050505050565b34156148e757600660009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156148d357600080fd5b505af115801561319d573d6000803e3d6000fd5b565b801580159061490257506009546001600160a01b031615155b1561495a57600954604080516356b4b2ad60e01b81523360048201526024810184905290516001600160a01b03909216916356b4b2ad9160448082019260009290919082900301818387803b1580156148d357600080fd5b50565b6000614967615c3a565b604051806101a001604052808e6001600160a01b031681526020018d81526020018c6001600160a01b031681526020018b81526020018a815260200189815260200188151581526020018781526020018681526020014381526020014281526020018515158152602001846001600160a01b031681525090506000806149ec83615600565b915091508e6001600160a01b03167f5265bc4952da402633b3fc35f67ab4245493a0ab94dd8ab123667c8d45a4485c8f8f8f8f8f8f8f8f8b60016012805490500343423a60405180806020018e6001600160a01b031681526020018d81526020018c81526020018b81526020018a1515815260200189815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528f818151815260200191508051906020019060200280838360005b83811015614ac3578181015183820152602001614aab565b505050509050019e50505050505050505050505050505060405180910390a29e9d5050505050505050505050505050565b6000614afe615b85565b604051806101c001604052808f6001600160a01b031681526020018e81526020018d6001600160a01b031681526020018c81526020018b81526020018a15158152602001896001600160a01b031681526020018881526020018781526020018681526020014381526020014281526020018515158152602001846001600160a01b03168152509050600080614b928361579e565b9150915082600001516001600160a01b03167f81ed0476a7e785a9e4728fffd679ea97176ca1ac85e1003462558bb5677da57b84602001518560400151866060015187608001518860a001518960c001518a60e001518b61010001518c61012001518c600160138054905003434260405180806020018e6001600160a01b031681526020018d81526020018c81526020018b151581526020018a6001600160a01b0316815260200189815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528f818151815260200191508051906020019060200280838360005b83811015614c9c578181015183820152602001614c84565b505050509050019e50505050505050505050505050505060405180910390a29f9e505050505050505050505050505050565b600082820183811015613afe576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6009546001600160a01b031680614d3d5750611b07565b600080826001600160a01b031663534ef883866040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050604080518083038186803b158015614d8c57600080fd5b505afa158015614da0573d6000803e3d6000fd5b505050506040513d6040811015614db657600080fd5b508051602090910151909250905081614dd157505050611b07565b7f474c763ff84bf2c2039a6d9fea955ecd0f724030e3c365b91169c6a16fe751b78585600360009054906101000a90046001600160a01b03166001600160a01b031663318bc6896040518163ffffffff1660e01b815260040160206040518083038186803b158015614e4257600080fd5b505afa158015614e56573d6000803e3d6000fd5b505050506040513d6020811015614e6c57600080fd5b5051604080516001600160a01b03948516815260208101939093528281019190915260608201869052918416608082015290519081900360a00190a15050505050565b6060614f04826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166159479092919063ffffffff16565b805190915015613ff057808060200190516020811015614f2357600080fd5b5051613ff05760405162461bcd60e51b815260040180806020018281038252602a815260200180615f71602a913960400191505060405180910390fd5b60035460408051634998b10960e11b81526001600160a01b03878116600483015286811660248301528481166044830152915160009384931691639331621291606480830192602092919082900301818787803b158015614fc057600080fd5b505af1158015614fd4573d6000803e3d6000fd5b505050506040513d6020811015614fea57600080fd5b505190508381101561502d5760405162461bcd60e51b815260040180806020018281038252602b815260200180615dc9602b913960400191505060405180910390fd5b95945050505050565b600081848411156150c55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561508a578181015183820152602001615072565b50505050905090810190601f1680156150b75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b3b151590565b6000826150e25750600061446b565b816150ef5750600161446b565b60008660018851038151811061510157fe5b602090810291909101015160035460408051634a3f088d60e01b81526001600160a01b038c81166004830152808516602483015289811660448301528815156064830152915193945091169160009182918491634a3f088d91608480830192610100929190829003018186803b15801561517a57600080fd5b505afa15801561518e573d6000803e3d6000fd5b505050506040513d6101008110156151a557600080fd5b5080516020909101519092509050816151c557600094505050505061446b565b60006151d18388614cce565b90506000846001600160a01b0316630a48d5a9878d6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b15801561522a57600080fd5b505afa15801561523e573d6000803e3d6000fd5b505050506040513d602081101561525457600080fd5b5051905060006152648483614cce565b90506000615278856143d8886127106152b0565b90506000615299836143d860085461271001886152b090919063ffffffff16565b919091109f9e505050505050505050505050505050565b6000826152bf575060006128f5565b828202828482816152cc57fe5b0414613afe5760405162461bcd60e51b8152600401808060200182810382526021815260200180615ed36021913960400191505060405180910390fd5b6000613afe83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b81525061595e565b8061535257613ff0565b8115615445576001600160a01b0383166000908152600b6020526040902054801580159061540357506003546040805163783a2b6760e11b81526001600160a01b0387811660048301529151849361540193879391169163f07456ce91602480820192602092909190829003018186803b1580156153cf57600080fd5b505afa1580156153e3573d6000803e3d6000fd5b505050506040513d60208110156153f957600080fd5b505190614cce565b115b1561543f5760405162461bcd60e51b815260040180806020018281038252602e815260200180615d5b602e913960400191505060405180910390fd5b50613ff0565b6001600160a01b0383166000908152600c602052604090205480158015906154be57506003546040805163114f1b5560e31b81526001600160a01b038781166004830152915184936154bc938793911691638a78daa891602480820192602092909190829003018186803b1580156153cf57600080fd5b115b156142435760405162461bcd60e51b815260040180806020018281038252602f815260200180615ea4602f913960400191505060405180910390fd5b6009546001600160a01b0316806155115750611b07565b600080826001600160a01b031663534ef883866040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050604080518083038186803b15801561556057600080fd5b505afa158015615574573d6000803e3d6000fd5b505050506040513d604081101561558a57600080fd5b5080516020918201516003546040805163318bc68960e01b815290519396509194507fc2414023ce7002ee98557d1e7be21e5559073336f2217ee5f9b2e50fd85f71ee93899389936001600160a01b039093169263318bc689926004808301939192829003018186803b158015614e4257600080fd5b80516001600160a01b03811660009081526018602052604081205490918291829061562c906001614cce565b6001600160a01b038316600090815260186020526040812082905590915061565483836128b5565b6000818152601960209081526040909120885181546001600160a01b0319166001600160a01b03909116178155888201518051939450899391926156a092600185019290910190615cc1565b5060408201516002820180546001600160a01b039283166001600160a01b0319909116179055606083015160038301556080830151600483015560a0830151600583015560c083015160068301805491151560ff1992831617905560e084015160078401556101008085015160088501556101208501516009850155610140850151600a850155610160850151600b90940180546101809096015190931602610100600160a81b0319931515949091169390931791909116919091179055601280546001810182556000919091527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344401819055909350915050915091565b80516001600160a01b0381166000908152601a60205260408120549091829182906157ca906001614cce565b6001600160a01b0383166000908152601a602052604081208290559091506157f283836128b5565b6000818152601b60209081526040909120885181546001600160a01b0319166001600160a01b039091161781558882015180519394508993919261583e92600185019290910190615cc1565b5060408201516002820180546001600160a01b039283166001600160a01b0319909116179055606083015160038301556080830151600483015560a083015160058301805460c08601518416610100908102610100600160a81b031994151560ff199384161785161790925560e0860151600686015581860151600786015561012086015160088601556101408601516009860155610160860151600a860155610180860151600b90950180546101a090970151909416909102931515941693909317909216179055601380546001810182556000919091527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09001819055909350915050915091565b606061595684846000856159c3565b949350505050565b600081836159ad5760405162461bcd60e51b815260206004820181815283516024840152835190928392604490910191908501908083836000831561508a578181015183820152602001615072565b5060008385816159b957fe5b0495945050505050565b606082471015615a045760405162461bcd60e51b8152600401808060200182810382526026815260200180615e2e6026913960400191505060405180910390fd5b615a0d856150cd565b615a5e576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b60208310615a9d5780518252601f199092019160209182019101615a7e565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114615aff576040519150601f19603f3d011682016040523d82523d6000602084013e615b04565b606091505b5091509150615b14828286615b1f565b979650505050505050565b60608315615b2e575081613afe565b825115615b3e5782518084602001fd5b60405162461bcd60e51b815260206004820181815284516024840152845185939192839260440191908501908083836000831561508a578181015183820152602001615072565b604051806101c0016040528060006001600160a01b031681526020016060815260200160006001600160a01b03168152602001600081526020016000815260200160001515815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001600081526020016000815260200160001515815260200160006001600160a01b031681525090565b508054600082559060005260206000209081019061495a9190615d26565b604051806101a0016040528060006001600160a01b031681526020016060815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000151581526020016000815260200160008152602001600081526020016000815260200160001515815260200160006001600160a01b031681525090565b828054828255906000526020600020908101928215615d16579160200282015b82811115615d1657825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190615ce1565b50615d22929150615d3b565b5090565b5b80821115615d225760008155600101615d27565b5b80821115615d225780546001600160a01b0319168155600101615d3c56fe42617365506f736974696f6e4d616e616765723a206d617820676c6f62616c206c6f6e67732065786365656465645265656e7472616e637947756172643a207265656e7472616e742063616c6c00476f7665726e61626c653a20666f7262696464656e000000000000000000000042617365506f736974696f6e4d616e616765723a20696e73756666696369656e7420616d6f756e744f7574416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c42617365506f736974696f6e4d616e616765723a206d61726b207072696365206c6f776572207468616e206c696d697442617365506f736974696f6e4d616e616765723a20666f7262696464656e000042617365506f736974696f6e4d616e616765723a206d617820676c6f62616c2073686f727473206578636565646564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7742617365506f736974696f6e4d616e616765723a206d61726b20707269636520686967686572207468616e206c696d697442617365506f736974696f6e4d616e616765723a20696e76616c6964205f706174682e6c656e67746842617365506f736974696f6e4d616e616765723a20696e76616c69642073656e6465725361666545524332303a204552433230206f7065726174696f6e20646964206e6f742073756363656564a26469706673582212209ce5a3b6726e45235a68f6f370dd0252abe63f1c6342dc561eba4b4a5871f32764736f6c634300060c0033000000000000000000000000489ee077994b6658eafa855c308275ead8097c4a000000000000000000000000abbc5f99639c9b6bcb58544ddf04efa6802f406400000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000f58eec83ba28ddd79390b9e90c4d3ebff1d434da000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000005af3107a4000"; + public PositionRouterDeploymentBase() : base(BYTECODE) { } + public PositionRouterDeploymentBase(string byteCode) : base(byteCode) { } + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_router", 2)] + public virtual string Router { get; set; } + [Parameter("address", "_weth", 3)] + public virtual string Weth { get; set; } + [Parameter("address", "_shortsTracker", 4)] + public virtual string ShortsTracker { get; set; } + [Parameter("uint256", "_depositFee", 5)] + public virtual BigInteger DepositFee { get; set; } + [Parameter("uint256", "_minExecutionFee", 6)] + public virtual BigInteger MinExecutionFee { get; set; } + } + + public partial class BasisPointsDivisorFunction : BasisPointsDivisorFunctionBase { } + + [Function("BASIS_POINTS_DIVISOR", "uint256")] + public class BasisPointsDivisorFunctionBase : FunctionMessage + { + + } + + public partial class AdminFunction : AdminFunctionBase { } + + [Function("admin", "address")] + public class AdminFunctionBase : FunctionMessage + { + + } + + public partial class ApproveFunction : ApproveFunctionBase { } + + [Function("approve")] + public class ApproveFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("address", "_spender", 2)] + public virtual string Spender { get; set; } + [Parameter("uint256", "_amount", 3)] + public virtual BigInteger Amount { get; set; } + } + + public partial class CallbackGasLimitFunction : CallbackGasLimitFunctionBase { } + + [Function("callbackGasLimit", "uint256")] + public class CallbackGasLimitFunctionBase : FunctionMessage + { + + } + + public partial class CancelDecreasePositionFunction : CancelDecreasePositionFunctionBase { } + + [Function("cancelDecreasePosition", "bool")] + public class CancelDecreasePositionFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class CancelIncreasePositionFunction : CancelIncreasePositionFunctionBase { } + + [Function("cancelIncreasePosition", "bool")] + public class CancelIncreasePositionFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class CreateDecreasePositionFunction : CreateDecreasePositionFunctionBase { } + + [Function("createDecreasePosition", "bytes32")] + public class CreateDecreasePositionFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_acceptablePrice", 7)] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "_minOut", 8)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_executionFee", 9)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("bool", "_withdrawETH", 10)] + public virtual bool WithdrawETH { get; set; } + [Parameter("address", "_callbackTarget", 11)] + public virtual string CallbackTarget { get; set; } + } + + public partial class CreateIncreasePositionFunction : CreateIncreasePositionFunctionBase { } + + [Function("createIncreasePosition", "bytes32")] + public class CreateIncreasePositionFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_amountIn", 3)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "_minOut", 4)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_acceptablePrice", 7)] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "_executionFee", 8)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("bytes32", "_referralCode", 9)] + public virtual byte[] ReferralCode { get; set; } + [Parameter("address", "_callbackTarget", 10)] + public virtual string CallbackTarget { get; set; } + } + + public partial class CreateIncreasePositionETHFunction : CreateIncreasePositionETHFunctionBase { } + + [Function("createIncreasePositionETH", "bytes32")] + public class CreateIncreasePositionETHFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_acceptablePrice", 6)] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "_executionFee", 7)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("bytes32", "_referralCode", 8)] + public virtual byte[] ReferralCode { get; set; } + [Parameter("address", "_callbackTarget", 9)] + public virtual string CallbackTarget { get; set; } + } + + public partial class DecreasePositionRequestKeysFunction : DecreasePositionRequestKeysFunctionBase { } + + [Function("decreasePositionRequestKeys", "bytes32")] + public class DecreasePositionRequestKeysFunctionBase : FunctionMessage + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class DecreasePositionRequestKeysStartFunction : DecreasePositionRequestKeysStartFunctionBase { } + + [Function("decreasePositionRequestKeysStart", "uint256")] + public class DecreasePositionRequestKeysStartFunctionBase : FunctionMessage + { + + } + + public partial class DecreasePositionRequestsFunction : DecreasePositionRequestsFunctionBase { } + + [Function("decreasePositionRequests", typeof(DecreasePositionRequestsOutputDTO))] + public class DecreasePositionRequestsFunctionBase : FunctionMessage + { + [Parameter("bytes32", "", 1)] + public virtual byte[] ReturnValue1 { get; set; } + } + + public partial class DecreasePositionsIndexFunction : DecreasePositionsIndexFunctionBase { } + + [Function("decreasePositionsIndex", "uint256")] + public class DecreasePositionsIndexFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class DepositFeeFunction : DepositFeeFunctionBase { } + + [Function("depositFee", "uint256")] + public class DepositFeeFunctionBase : FunctionMessage + { + + } + + public partial class ExecuteDecreasePositionFunction : ExecuteDecreasePositionFunctionBase { } + + [Function("executeDecreasePosition", "bool")] + public class ExecuteDecreasePositionFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class ExecuteDecreasePositionsFunction : ExecuteDecreasePositionsFunctionBase { } + + [Function("executeDecreasePositions")] + public class ExecuteDecreasePositionsFunctionBase : FunctionMessage + { + [Parameter("uint256", "_endIndex", 1)] + public virtual BigInteger EndIndex { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class ExecuteIncreasePositionFunction : ExecuteIncreasePositionFunctionBase { } + + [Function("executeIncreasePosition", "bool")] + public class ExecuteIncreasePositionFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class ExecuteIncreasePositionsFunction : ExecuteIncreasePositionsFunctionBase { } + + [Function("executeIncreasePositions")] + public class ExecuteIncreasePositionsFunctionBase : FunctionMessage + { + [Parameter("uint256", "_endIndex", 1)] + public virtual BigInteger EndIndex { get; set; } + [Parameter("address", "_executionFeeReceiver", 2)] + public virtual string ExecutionFeeReceiver { get; set; } + } + + public partial class FeeReservesFunction : FeeReservesFunctionBase { } + + [Function("feeReserves", "uint256")] + public class FeeReservesFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class GetDecreasePositionRequestPathFunction : GetDecreasePositionRequestPathFunctionBase { } + + [Function("getDecreasePositionRequestPath", "address[]")] + public class GetDecreasePositionRequestPathFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + } + + public partial class GetIncreasePositionRequestPathFunction : GetIncreasePositionRequestPathFunctionBase { } + + [Function("getIncreasePositionRequestPath", "address[]")] + public class GetIncreasePositionRequestPathFunctionBase : FunctionMessage + { + [Parameter("bytes32", "_key", 1)] + public virtual byte[] Key { get; set; } + } + + public partial class GetRequestKeyFunction : GetRequestKeyFunctionBase { } + + [Function("getRequestKey", "bytes32")] + public class GetRequestKeyFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("uint256", "_index", 2)] + public virtual BigInteger Index { get; set; } + } + + public partial class GetRequestQueueLengthsFunction : GetRequestQueueLengthsFunctionBase { } + + [Function("getRequestQueueLengths", typeof(GetRequestQueueLengthsOutputDTO))] + public class GetRequestQueueLengthsFunctionBase : FunctionMessage + { + + } + + public partial class GovFunction : GovFunctionBase { } + + [Function("gov", "address")] + public class GovFunctionBase : FunctionMessage + { + + } + + public partial class IncreasePositionBufferBpsFunction : IncreasePositionBufferBpsFunctionBase { } + + [Function("increasePositionBufferBps", "uint256")] + public class IncreasePositionBufferBpsFunctionBase : FunctionMessage + { + + } + + public partial class IncreasePositionRequestKeysFunction : IncreasePositionRequestKeysFunctionBase { } + + [Function("increasePositionRequestKeys", "bytes32")] + public class IncreasePositionRequestKeysFunctionBase : FunctionMessage + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class IncreasePositionRequestKeysStartFunction : IncreasePositionRequestKeysStartFunctionBase { } + + [Function("increasePositionRequestKeysStart", "uint256")] + public class IncreasePositionRequestKeysStartFunctionBase : FunctionMessage + { + + } + + public partial class IncreasePositionRequestsFunction : IncreasePositionRequestsFunctionBase { } + + [Function("increasePositionRequests", typeof(IncreasePositionRequestsOutputDTO))] + public class IncreasePositionRequestsFunctionBase : FunctionMessage + { + [Parameter("bytes32", "", 1)] + public virtual byte[] ReturnValue1 { get; set; } + } + + public partial class IncreasePositionsIndexFunction : IncreasePositionsIndexFunctionBase { } + + [Function("increasePositionsIndex", "uint256")] + public class IncreasePositionsIndexFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class IsLeverageEnabledFunction : IsLeverageEnabledFunctionBase { } + + [Function("isLeverageEnabled", "bool")] + public class IsLeverageEnabledFunctionBase : FunctionMessage + { + + } + + public partial class IsPositionKeeperFunction : IsPositionKeeperFunctionBase { } + + [Function("isPositionKeeper", "bool")] + public class IsPositionKeeperFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class MaxGlobalLongSizesFunction : MaxGlobalLongSizesFunctionBase { } + + [Function("maxGlobalLongSizes", "uint256")] + public class MaxGlobalLongSizesFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class MaxGlobalShortSizesFunction : MaxGlobalShortSizesFunctionBase { } + + [Function("maxGlobalShortSizes", "uint256")] + public class MaxGlobalShortSizesFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class MaxTimeDelayFunction : MaxTimeDelayFunctionBase { } + + [Function("maxTimeDelay", "uint256")] + public class MaxTimeDelayFunctionBase : FunctionMessage + { + + } + + public partial class MinBlockDelayKeeperFunction : MinBlockDelayKeeperFunctionBase { } + + [Function("minBlockDelayKeeper", "uint256")] + public class MinBlockDelayKeeperFunctionBase : FunctionMessage + { + + } + + public partial class MinExecutionFeeFunction : MinExecutionFeeFunctionBase { } + + [Function("minExecutionFee", "uint256")] + public class MinExecutionFeeFunctionBase : FunctionMessage + { + + } + + public partial class MinTimeDelayPublicFunction : MinTimeDelayPublicFunctionBase { } + + [Function("minTimeDelayPublic", "uint256")] + public class MinTimeDelayPublicFunctionBase : FunctionMessage + { + + } + + public partial class ReferralStorageFunction : ReferralStorageFunctionBase { } + + [Function("referralStorage", "address")] + public class ReferralStorageFunctionBase : FunctionMessage + { + + } + + public partial class RouterFunction : RouterFunctionBase { } + + [Function("router", "address")] + public class RouterFunctionBase : FunctionMessage + { + + } + + public partial class SendValueFunction : SendValueFunctionBase { } + + [Function("sendValue")] + public class SendValueFunctionBase : FunctionMessage + { + [Parameter("address", "_receiver", 1)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_amount", 2)] + public virtual BigInteger Amount { get; set; } + } + + public partial class SetAdminFunction : SetAdminFunctionBase { } + + [Function("setAdmin")] + public class SetAdminFunctionBase : FunctionMessage + { + [Parameter("address", "_admin", 1)] + public virtual string Admin { get; set; } + } + + public partial class SetCallbackGasLimitFunction : SetCallbackGasLimitFunctionBase { } + + [Function("setCallbackGasLimit")] + public class SetCallbackGasLimitFunctionBase : FunctionMessage + { + [Parameter("uint256", "_callbackGasLimit", 1)] + public virtual BigInteger CallbackGasLimit { get; set; } + } + + public partial class SetDelayValuesFunction : SetDelayValuesFunctionBase { } + + [Function("setDelayValues")] + public class SetDelayValuesFunctionBase : FunctionMessage + { + [Parameter("uint256", "_minBlockDelayKeeper", 1)] + public virtual BigInteger MinBlockDelayKeeper { get; set; } + [Parameter("uint256", "_minTimeDelayPublic", 2)] + public virtual BigInteger MinTimeDelayPublic { get; set; } + [Parameter("uint256", "_maxTimeDelay", 3)] + public virtual BigInteger MaxTimeDelay { get; set; } + } + + public partial class SetDepositFeeFunction : SetDepositFeeFunctionBase { } + + [Function("setDepositFee")] + public class SetDepositFeeFunctionBase : FunctionMessage + { + [Parameter("uint256", "_depositFee", 1)] + public virtual BigInteger DepositFee { get; set; } + } + + public partial class SetGovFunction : SetGovFunctionBase { } + + [Function("setGov")] + public class SetGovFunctionBase : FunctionMessage + { + [Parameter("address", "_gov", 1)] + public virtual string Gov { get; set; } + } + + public partial class SetIncreasePositionBufferBpsFunction : SetIncreasePositionBufferBpsFunctionBase { } + + [Function("setIncreasePositionBufferBps")] + public class SetIncreasePositionBufferBpsFunctionBase : FunctionMessage + { + [Parameter("uint256", "_increasePositionBufferBps", 1)] + public virtual BigInteger IncreasePositionBufferBps { get; set; } + } + + public partial class SetIsLeverageEnabledFunction : SetIsLeverageEnabledFunctionBase { } + + [Function("setIsLeverageEnabled")] + public class SetIsLeverageEnabledFunctionBase : FunctionMessage + { + [Parameter("bool", "_isLeverageEnabled", 1)] + public virtual bool IsLeverageEnabled { get; set; } + } + + public partial class SetMaxGlobalSizesFunction : SetMaxGlobalSizesFunctionBase { } + + [Function("setMaxGlobalSizes")] + public class SetMaxGlobalSizesFunctionBase : FunctionMessage + { + [Parameter("address[]", "_tokens", 1)] + public virtual List Tokens { get; set; } + [Parameter("uint256[]", "_longSizes", 2)] + public virtual List LongSizes { get; set; } + [Parameter("uint256[]", "_shortSizes", 3)] + public virtual List ShortSizes { get; set; } + } + + public partial class SetMinExecutionFeeFunction : SetMinExecutionFeeFunctionBase { } + + [Function("setMinExecutionFee")] + public class SetMinExecutionFeeFunctionBase : FunctionMessage + { + [Parameter("uint256", "_minExecutionFee", 1)] + public virtual BigInteger MinExecutionFee { get; set; } + } + + public partial class SetPositionKeeperFunction : SetPositionKeeperFunctionBase { } + + [Function("setPositionKeeper")] + public class SetPositionKeeperFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("bool", "_isActive", 2)] + public virtual bool IsActive { get; set; } + } + + public partial class SetReferralStorageFunction : SetReferralStorageFunctionBase { } + + [Function("setReferralStorage")] + public class SetReferralStorageFunctionBase : FunctionMessage + { + [Parameter("address", "_referralStorage", 1)] + public virtual string ReferralStorage { get; set; } + } + + public partial class SetRequestKeysStartValuesFunction : SetRequestKeysStartValuesFunctionBase { } + + [Function("setRequestKeysStartValues")] + public class SetRequestKeysStartValuesFunctionBase : FunctionMessage + { + [Parameter("uint256", "_increasePositionRequestKeysStart", 1)] + public virtual BigInteger IncreasePositionRequestKeysStart { get; set; } + [Parameter("uint256", "_decreasePositionRequestKeysStart", 2)] + public virtual BigInteger DecreasePositionRequestKeysStart { get; set; } + } + + public partial class ShortsTrackerFunction : ShortsTrackerFunctionBase { } + + [Function("shortsTracker", "address")] + public class ShortsTrackerFunctionBase : FunctionMessage + { + + } + + public partial class VaultFunction : VaultFunctionBase { } + + [Function("vault", "address")] + public class VaultFunctionBase : FunctionMessage + { + + } + + public partial class WethFunction : WethFunctionBase { } + + [Function("weth", "address")] + public class WethFunctionBase : FunctionMessage + { + + } + + public partial class WithdrawFeesFunction : WithdrawFeesFunctionBase { } + + [Function("withdrawFees")] + public class WithdrawFeesFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("address", "_receiver", 2)] + public virtual string Receiver { get; set; } + } + + public partial class CallbackEventDTO : CallbackEventDTOBase { } + + [Event("Callback")] + public class CallbackEventDTOBase : IEventDTO + { + [Parameter("address", "callbackTarget", 1, false )] + public virtual string CallbackTarget { get; set; } + [Parameter("bool", "success", 2, false )] + public virtual bool Success { get; set; } + } + + public partial class CancelDecreasePositionEventDTO : CancelDecreasePositionEventDTOBase { } + + [Event("CancelDecreasePosition")] + public class CancelDecreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "sizeDelta", 5, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6, false )] + public virtual bool IsLong { get; set; } + [Parameter("address", "receiver", 7, false )] + public virtual string Receiver { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "minOut", 9, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockGap", 11, false )] + public virtual BigInteger BlockGap { get; set; } + [Parameter("uint256", "timeGap", 12, false )] + public virtual BigInteger TimeGap { get; set; } + } + + public partial class CancelIncreasePositionEventDTO : CancelIncreasePositionEventDTOBase { } + + [Event("CancelIncreasePosition")] + public class CancelIncreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockGap", 10, false )] + public virtual BigInteger BlockGap { get; set; } + [Parameter("uint256", "timeGap", 11, false )] + public virtual BigInteger TimeGap { get; set; } + } + + public partial class CreateDecreasePositionEventDTO : CreateDecreasePositionEventDTOBase { } + + [Event("CreateDecreasePosition")] + public class CreateDecreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "sizeDelta", 5, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6, false )] + public virtual bool IsLong { get; set; } + [Parameter("address", "receiver", 7, false )] + public virtual string Receiver { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "minOut", 9, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "index", 11, false )] + public virtual BigInteger Index { get; set; } + [Parameter("uint256", "queueIndex", 12, false )] + public virtual BigInteger QueueIndex { get; set; } + [Parameter("uint256", "blockNumber", 13, false )] + public virtual BigInteger BlockNumber { get; set; } + [Parameter("uint256", "blockTime", 14, false )] + public virtual BigInteger BlockTime { get; set; } + } + + public partial class CreateIncreasePositionEventDTO : CreateIncreasePositionEventDTOBase { } + + [Event("CreateIncreasePosition")] + public class CreateIncreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "index", 10, false )] + public virtual BigInteger Index { get; set; } + [Parameter("uint256", "queueIndex", 11, false )] + public virtual BigInteger QueueIndex { get; set; } + [Parameter("uint256", "blockNumber", 12, false )] + public virtual BigInteger BlockNumber { get; set; } + [Parameter("uint256", "blockTime", 13, false )] + public virtual BigInteger BlockTime { get; set; } + [Parameter("uint256", "gasPrice", 14, false )] + public virtual BigInteger GasPrice { get; set; } + } + + public partial class DecreasePositionReferralEventDTO : DecreasePositionReferralEventDTOBase { } + + [Event("DecreasePositionReferral")] + public class DecreasePositionReferralEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, false )] + public virtual string Account { get; set; } + [Parameter("uint256", "sizeDelta", 2, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("uint256", "marginFeeBasisPoints", 3, false )] + public virtual BigInteger MarginFeeBasisPoints { get; set; } + [Parameter("bytes32", "referralCode", 4, false )] + public virtual byte[] ReferralCode { get; set; } + [Parameter("address", "referrer", 5, false )] + public virtual string Referrer { get; set; } + } + + public partial class ExecuteDecreasePositionEventDTO : ExecuteDecreasePositionEventDTOBase { } + + [Event("ExecuteDecreasePosition")] + public class ExecuteDecreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "collateralDelta", 4, false )] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "sizeDelta", 5, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6, false )] + public virtual bool IsLong { get; set; } + [Parameter("address", "receiver", 7, false )] + public virtual string Receiver { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "minOut", 9, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "executionFee", 10, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockGap", 11, false )] + public virtual BigInteger BlockGap { get; set; } + [Parameter("uint256", "timeGap", 12, false )] + public virtual BigInteger TimeGap { get; set; } + } + + public partial class ExecuteIncreasePositionEventDTO : ExecuteIncreasePositionEventDTOBase { } + + [Event("ExecuteIncreasePosition")] + public class ExecuteIncreasePositionEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("address[]", "path", 2, false )] + public virtual List Path { get; set; } + [Parameter("address", "indexToken", 3, false )] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 5, false )] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "sizeDelta", 6, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 7, false )] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "acceptablePrice", 8, false )] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "executionFee", 9, false )] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockGap", 10, false )] + public virtual BigInteger BlockGap { get; set; } + [Parameter("uint256", "timeGap", 11, false )] + public virtual BigInteger TimeGap { get; set; } + } + + public partial class IncreasePositionReferralEventDTO : IncreasePositionReferralEventDTOBase { } + + [Event("IncreasePositionReferral")] + public class IncreasePositionReferralEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, false )] + public virtual string Account { get; set; } + [Parameter("uint256", "sizeDelta", 2, false )] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("uint256", "marginFeeBasisPoints", 3, false )] + public virtual BigInteger MarginFeeBasisPoints { get; set; } + [Parameter("bytes32", "referralCode", 4, false )] + public virtual byte[] ReferralCode { get; set; } + [Parameter("address", "referrer", 5, false )] + public virtual string Referrer { get; set; } + } + + public partial class SetAdminEventDTO : SetAdminEventDTOBase { } + + [Event("SetAdmin")] + public class SetAdminEventDTOBase : IEventDTO + { + [Parameter("address", "admin", 1, false )] + public virtual string Admin { get; set; } + } + + public partial class SetCallbackGasLimitEventDTO : SetCallbackGasLimitEventDTOBase { } + + [Event("SetCallbackGasLimit")] + public class SetCallbackGasLimitEventDTOBase : IEventDTO + { + [Parameter("uint256", "callbackGasLimit", 1, false )] + public virtual BigInteger CallbackGasLimit { get; set; } + } + + public partial class SetDelayValuesEventDTO : SetDelayValuesEventDTOBase { } + + [Event("SetDelayValues")] + public class SetDelayValuesEventDTOBase : IEventDTO + { + [Parameter("uint256", "minBlockDelayKeeper", 1, false )] + public virtual BigInteger MinBlockDelayKeeper { get; set; } + [Parameter("uint256", "minTimeDelayPublic", 2, false )] + public virtual BigInteger MinTimeDelayPublic { get; set; } + [Parameter("uint256", "maxTimeDelay", 3, false )] + public virtual BigInteger MaxTimeDelay { get; set; } + } + + public partial class SetDepositFeeEventDTO : SetDepositFeeEventDTOBase { } + + [Event("SetDepositFee")] + public class SetDepositFeeEventDTOBase : IEventDTO + { + [Parameter("uint256", "depositFee", 1, false )] + public virtual BigInteger DepositFee { get; set; } + } + + public partial class SetIncreasePositionBufferBpsEventDTO : SetIncreasePositionBufferBpsEventDTOBase { } + + [Event("SetIncreasePositionBufferBps")] + public class SetIncreasePositionBufferBpsEventDTOBase : IEventDTO + { + [Parameter("uint256", "increasePositionBufferBps", 1, false )] + public virtual BigInteger IncreasePositionBufferBps { get; set; } + } + + public partial class SetIsLeverageEnabledEventDTO : SetIsLeverageEnabledEventDTOBase { } + + [Event("SetIsLeverageEnabled")] + public class SetIsLeverageEnabledEventDTOBase : IEventDTO + { + [Parameter("bool", "isLeverageEnabled", 1, false )] + public virtual bool IsLeverageEnabled { get; set; } + } + + public partial class SetMaxGlobalSizesEventDTO : SetMaxGlobalSizesEventDTOBase { } + + [Event("SetMaxGlobalSizes")] + public class SetMaxGlobalSizesEventDTOBase : IEventDTO + { + [Parameter("address[]", "tokens", 1, false )] + public virtual List Tokens { get; set; } + [Parameter("uint256[]", "longSizes", 2, false )] + public virtual List LongSizes { get; set; } + [Parameter("uint256[]", "shortSizes", 3, false )] + public virtual List ShortSizes { get; set; } + } + + public partial class SetMinExecutionFeeEventDTO : SetMinExecutionFeeEventDTOBase { } + + [Event("SetMinExecutionFee")] + public class SetMinExecutionFeeEventDTOBase : IEventDTO + { + [Parameter("uint256", "minExecutionFee", 1, false )] + public virtual BigInteger MinExecutionFee { get; set; } + } + + public partial class SetPositionKeeperEventDTO : SetPositionKeeperEventDTOBase { } + + [Event("SetPositionKeeper")] + public class SetPositionKeeperEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, true )] + public virtual string Account { get; set; } + [Parameter("bool", "isActive", 2, false )] + public virtual bool IsActive { get; set; } + } + + public partial class SetReferralStorageEventDTO : SetReferralStorageEventDTOBase { } + + [Event("SetReferralStorage")] + public class SetReferralStorageEventDTOBase : IEventDTO + { + [Parameter("address", "referralStorage", 1, false )] + public virtual string ReferralStorage { get; set; } + } + + public partial class SetRequestKeysStartValuesEventDTO : SetRequestKeysStartValuesEventDTOBase { } + + [Event("SetRequestKeysStartValues")] + public class SetRequestKeysStartValuesEventDTOBase : IEventDTO + { + [Parameter("uint256", "increasePositionRequestKeysStart", 1, false )] + public virtual BigInteger IncreasePositionRequestKeysStart { get; set; } + [Parameter("uint256", "decreasePositionRequestKeysStart", 2, false )] + public virtual BigInteger DecreasePositionRequestKeysStart { get; set; } + } + + public partial class WithdrawFeesEventDTO : WithdrawFeesEventDTOBase { } + + [Event("WithdrawFees")] + public class WithdrawFeesEventDTOBase : IEventDTO + { + [Parameter("address", "token", 1, false )] + public virtual string Token { get; set; } + [Parameter("address", "receiver", 2, false )] + public virtual string Receiver { get; set; } + [Parameter("uint256", "amount", 3, false )] + public virtual BigInteger Amount { get; set; } + } + + public partial class BasisPointsDivisorOutputDTO : BasisPointsDivisorOutputDTOBase { } + + [FunctionOutput] + public class BasisPointsDivisorOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class AdminOutputDTO : AdminOutputDTOBase { } + + [FunctionOutput] + public class AdminOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + + + public partial class CallbackGasLimitOutputDTO : CallbackGasLimitOutputDTOBase { } + + [FunctionOutput] + public class CallbackGasLimitOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + + + + + + + + + public partial class DecreasePositionRequestKeysOutputDTO : DecreasePositionRequestKeysOutputDTOBase { } + + [FunctionOutput] + public class DecreasePositionRequestKeysOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bytes32", "", 1)] + public virtual byte[] ReturnValue1 { get; set; } + } + + public partial class DecreasePositionRequestKeysStartOutputDTO : DecreasePositionRequestKeysStartOutputDTOBase { } + + [FunctionOutput] + public class DecreasePositionRequestKeysStartOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class DecreasePositionRequestsOutputDTO : DecreasePositionRequestsOutputDTOBase { } + + [FunctionOutput] + public class DecreasePositionRequestsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "acceptablePrice", 7)] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "minOut", 8)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "executionFee", 9)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockNumber", 10)] + public virtual BigInteger BlockNumber { get; set; } + [Parameter("uint256", "blockTime", 11)] + public virtual BigInteger BlockTime { get; set; } + [Parameter("bool", "withdrawETH", 12)] + public virtual bool WithdrawETH { get; set; } + [Parameter("address", "callbackTarget", 13)] + public virtual string CallbackTarget { get; set; } + } + + public partial class DecreasePositionsIndexOutputDTO : DecreasePositionsIndexOutputDTOBase { } + + [FunctionOutput] + public class DecreasePositionsIndexOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class DepositFeeOutputDTO : DepositFeeOutputDTOBase { } + + [FunctionOutput] + public class DepositFeeOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + + + + + + + + + public partial class FeeReservesOutputDTO : FeeReservesOutputDTOBase { } + + [FunctionOutput] + public class FeeReservesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GetDecreasePositionRequestPathOutputDTO : GetDecreasePositionRequestPathOutputDTOBase { } + + [FunctionOutput] + public class GetDecreasePositionRequestPathOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetIncreasePositionRequestPathOutputDTO : GetIncreasePositionRequestPathOutputDTOBase { } + + [FunctionOutput] + public class GetIncreasePositionRequestPathOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetRequestKeyOutputDTO : GetRequestKeyOutputDTOBase { } + + [FunctionOutput] + public class GetRequestKeyOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bytes32", "", 1)] + public virtual byte[] ReturnValue1 { get; set; } + } + + public partial class GetRequestQueueLengthsOutputDTO : GetRequestQueueLengthsOutputDTOBase { } + + [FunctionOutput] + public class GetRequestQueueLengthsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + [Parameter("uint256", "", 3)] + public virtual BigInteger ReturnValue3 { get; set; } + [Parameter("uint256", "", 4)] + public virtual BigInteger ReturnValue4 { get; set; } + } + + public partial class GovOutputDTO : GovOutputDTOBase { } + + [FunctionOutput] + public class GovOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class IncreasePositionBufferBpsOutputDTO : IncreasePositionBufferBpsOutputDTOBase { } + + [FunctionOutput] + public class IncreasePositionBufferBpsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class IncreasePositionRequestKeysOutputDTO : IncreasePositionRequestKeysOutputDTOBase { } + + [FunctionOutput] + public class IncreasePositionRequestKeysOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bytes32", "", 1)] + public virtual byte[] ReturnValue1 { get; set; } + } + + public partial class IncreasePositionRequestKeysStartOutputDTO : IncreasePositionRequestKeysStartOutputDTOBase { } + + [FunctionOutput] + public class IncreasePositionRequestKeysStartOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class IncreasePositionRequestsOutputDTO : IncreasePositionRequestsOutputDTOBase { } + + [FunctionOutput] + public class IncreasePositionRequestsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "amountIn", 3)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "minOut", 4)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "acceptablePrice", 7)] + public virtual BigInteger AcceptablePrice { get; set; } + [Parameter("uint256", "executionFee", 8)] + public virtual BigInteger ExecutionFee { get; set; } + [Parameter("uint256", "blockNumber", 9)] + public virtual BigInteger BlockNumber { get; set; } + [Parameter("uint256", "blockTime", 10)] + public virtual BigInteger BlockTime { get; set; } + [Parameter("bool", "hasCollateralInETH", 11)] + public virtual bool HasCollateralInETH { get; set; } + [Parameter("address", "callbackTarget", 12)] + public virtual string CallbackTarget { get; set; } + } + + public partial class IncreasePositionsIndexOutputDTO : IncreasePositionsIndexOutputDTOBase { } + + [FunctionOutput] + public class IncreasePositionsIndexOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class IsLeverageEnabledOutputDTO : IsLeverageEnabledOutputDTOBase { } + + [FunctionOutput] + public class IsLeverageEnabledOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + public partial class IsPositionKeeperOutputDTO : IsPositionKeeperOutputDTOBase { } + + [FunctionOutput] + public class IsPositionKeeperOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + public partial class MaxGlobalLongSizesOutputDTO : MaxGlobalLongSizesOutputDTOBase { } + + [FunctionOutput] + public class MaxGlobalLongSizesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MaxGlobalShortSizesOutputDTO : MaxGlobalShortSizesOutputDTOBase { } + + [FunctionOutput] + public class MaxGlobalShortSizesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MaxTimeDelayOutputDTO : MaxTimeDelayOutputDTOBase { } + + [FunctionOutput] + public class MaxTimeDelayOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MinBlockDelayKeeperOutputDTO : MinBlockDelayKeeperOutputDTOBase { } + + [FunctionOutput] + public class MinBlockDelayKeeperOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MinExecutionFeeOutputDTO : MinExecutionFeeOutputDTOBase { } + + [FunctionOutput] + public class MinExecutionFeeOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class MinTimeDelayPublicOutputDTO : MinTimeDelayPublicOutputDTOBase { } + + [FunctionOutput] + public class MinTimeDelayPublicOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class ReferralStorageOutputDTO : ReferralStorageOutputDTOBase { } + + [FunctionOutput] + public class ReferralStorageOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class RouterOutputDTO : RouterOutputDTOBase { } + + [FunctionOutput] + public class RouterOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + public partial class ShortsTrackerOutputDTO : ShortsTrackerOutputDTOBase { } + + [FunctionOutput] + public class ShortsTrackerOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class VaultOutputDTO : VaultOutputDTOBase { } + + [FunctionOutput] + public class VaultOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class WethOutputDTO : WethOutputDTOBase { } + + [FunctionOutput] + public class WethOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + +} diff --git a/src/Managing.Tools.ABI/PositionRouter/PositionRouterService.cs b/src/Managing.Tools.ABI/PositionRouter/PositionRouterService.cs new file mode 100644 index 0000000..3655814 --- /dev/null +++ b/src/Managing.Tools.ABI/PositionRouter/PositionRouterService.cs @@ -0,0 +1,1142 @@ +using System.Numerics; +using Nethereum.Web3; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Contracts.ContractHandlers; +using Managing.Tools.PositionRouter.ContractDefinition; + +namespace Managing.Tools.PositionRouter +{ + public partial class PositionRouterService + { + public static Task DeployContractAndWaitForReceiptAsync(Web3 web3, PositionRouterDeployment positionRouterDeployment, CancellationTokenSource cancellationTokenSource = null) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAndWaitForReceiptAsync(positionRouterDeployment, cancellationTokenSource); + } + + public static Task DeployContractAsync(Web3 web3, PositionRouterDeployment positionRouterDeployment) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAsync(positionRouterDeployment); + } + + public static async Task DeployContractAndGetServiceAsync(Web3 web3, PositionRouterDeployment positionRouterDeployment, CancellationTokenSource cancellationTokenSource = null) + { + var receipt = await DeployContractAndWaitForReceiptAsync(web3, positionRouterDeployment, cancellationTokenSource); + return new PositionRouterService(web3, receipt.ContractAddress); + } + + protected IWeb3 Web3 { get; } + + public ContractHandler ContractHandler { get; } + + public PositionRouterService(Web3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public PositionRouterService(IWeb3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public Task BasisPointsDivisorQueryAsync(BasisPointsDivisorFunction basisPointsDivisorFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(basisPointsDivisorFunction, blockParameter); + } + + + public Task BasisPointsDivisorQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task AdminQueryAsync(AdminFunction adminFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(adminFunction, blockParameter); + } + + + public Task AdminQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task ApproveRequestAsync(ApproveFunction approveFunction) + { + return ContractHandler.SendRequestAsync(approveFunction); + } + + public Task ApproveRequestAndWaitForReceiptAsync(ApproveFunction approveFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(approveFunction, cancellationToken); + } + + public Task ApproveRequestAsync(string token, string spender, BigInteger amount) + { + var approveFunction = new ApproveFunction(); + approveFunction.Token = token; + approveFunction.Spender = spender; + approveFunction.Amount = amount; + + return ContractHandler.SendRequestAsync(approveFunction); + } + + public Task ApproveRequestAndWaitForReceiptAsync(string token, string spender, BigInteger amount, CancellationTokenSource cancellationToken = null) + { + var approveFunction = new ApproveFunction(); + approveFunction.Token = token; + approveFunction.Spender = spender; + approveFunction.Amount = amount; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(approveFunction, cancellationToken); + } + + public Task CallbackGasLimitQueryAsync(CallbackGasLimitFunction callbackGasLimitFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(callbackGasLimitFunction, blockParameter); + } + + + public Task CallbackGasLimitQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task CancelDecreasePositionRequestAsync(CancelDecreasePositionFunction cancelDecreasePositionFunction) + { + return ContractHandler.SendRequestAsync(cancelDecreasePositionFunction); + } + + public Task CancelDecreasePositionRequestAndWaitForReceiptAsync(CancelDecreasePositionFunction cancelDecreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelDecreasePositionFunction, cancellationToken); + } + + public Task CancelDecreasePositionRequestAsync(byte[] key, string executionFeeReceiver) + { + var cancelDecreasePositionFunction = new CancelDecreasePositionFunction(); + cancelDecreasePositionFunction.Key = key; + cancelDecreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(cancelDecreasePositionFunction); + } + + public Task CancelDecreasePositionRequestAndWaitForReceiptAsync(byte[] key, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var cancelDecreasePositionFunction = new CancelDecreasePositionFunction(); + cancelDecreasePositionFunction.Key = key; + cancelDecreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelDecreasePositionFunction, cancellationToken); + } + + public Task CancelIncreasePositionRequestAsync(CancelIncreasePositionFunction cancelIncreasePositionFunction) + { + return ContractHandler.SendRequestAsync(cancelIncreasePositionFunction); + } + + public Task CancelIncreasePositionRequestAndWaitForReceiptAsync(CancelIncreasePositionFunction cancelIncreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelIncreasePositionFunction, cancellationToken); + } + + public Task CancelIncreasePositionRequestAsync(byte[] key, string executionFeeReceiver) + { + var cancelIncreasePositionFunction = new CancelIncreasePositionFunction(); + cancelIncreasePositionFunction.Key = key; + cancelIncreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(cancelIncreasePositionFunction); + } + + public Task CancelIncreasePositionRequestAndWaitForReceiptAsync(byte[] key, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var cancelIncreasePositionFunction = new CancelIncreasePositionFunction(); + cancelIncreasePositionFunction.Key = key; + cancelIncreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(cancelIncreasePositionFunction, cancellationToken); + } + + public Task CreateDecreasePositionRequestAsync(CreateDecreasePositionFunction createDecreasePositionFunction) + { + return ContractHandler.SendRequestAsync(createDecreasePositionFunction); + } + + public Task CreateDecreasePositionRequestAndWaitForReceiptAsync(CreateDecreasePositionFunction createDecreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createDecreasePositionFunction, cancellationToken); + } + + public Task CreateDecreasePositionRequestAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger acceptablePrice, BigInteger minOut, BigInteger executionFee, bool withdrawETH, string callbackTarget) + { + var createDecreasePositionFunction = new CreateDecreasePositionFunction(); + createDecreasePositionFunction.Path = path; + createDecreasePositionFunction.IndexToken = indexToken; + createDecreasePositionFunction.CollateralDelta = collateralDelta; + createDecreasePositionFunction.SizeDelta = sizeDelta; + createDecreasePositionFunction.IsLong = isLong; + createDecreasePositionFunction.Receiver = receiver; + createDecreasePositionFunction.AcceptablePrice = acceptablePrice; + createDecreasePositionFunction.MinOut = minOut; + createDecreasePositionFunction.ExecutionFee = executionFee; + createDecreasePositionFunction.WithdrawETH = withdrawETH; + createDecreasePositionFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAsync(createDecreasePositionFunction); + } + + public Task CreateDecreasePositionRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger acceptablePrice, BigInteger minOut, BigInteger executionFee, bool withdrawETH, string callbackTarget, CancellationTokenSource cancellationToken = null) + { + var createDecreasePositionFunction = new CreateDecreasePositionFunction(); + createDecreasePositionFunction.Path = path; + createDecreasePositionFunction.IndexToken = indexToken; + createDecreasePositionFunction.CollateralDelta = collateralDelta; + createDecreasePositionFunction.SizeDelta = sizeDelta; + createDecreasePositionFunction.IsLong = isLong; + createDecreasePositionFunction.Receiver = receiver; + createDecreasePositionFunction.AcceptablePrice = acceptablePrice; + createDecreasePositionFunction.MinOut = minOut; + createDecreasePositionFunction.ExecutionFee = executionFee; + createDecreasePositionFunction.WithdrawETH = withdrawETH; + createDecreasePositionFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createDecreasePositionFunction, cancellationToken); + } + + public Task CreateIncreasePositionRequestAsync(CreateIncreasePositionFunction createIncreasePositionFunction) + { + return ContractHandler.SendRequestAsync(createIncreasePositionFunction); + } + + public Task CreateIncreasePositionRequestAndWaitForReceiptAsync(CreateIncreasePositionFunction createIncreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreasePositionFunction, cancellationToken); + } + + public Task CreateIncreasePositionRequestAsync(List path, string indexToken, BigInteger amountIn, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger acceptablePrice, BigInteger executionFee, byte[] referralCode, string callbackTarget) + { + var createIncreasePositionFunction = new CreateIncreasePositionFunction(); + createIncreasePositionFunction.Path = path; + createIncreasePositionFunction.IndexToken = indexToken; + createIncreasePositionFunction.AmountIn = amountIn; + createIncreasePositionFunction.MinOut = minOut; + createIncreasePositionFunction.SizeDelta = sizeDelta; + createIncreasePositionFunction.IsLong = isLong; + createIncreasePositionFunction.AcceptablePrice = acceptablePrice; + createIncreasePositionFunction.ExecutionFee = executionFee; + createIncreasePositionFunction.ReferralCode = referralCode; + createIncreasePositionFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAsync(createIncreasePositionFunction); + } + + public Task CreateIncreasePositionRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger amountIn, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger acceptablePrice, BigInteger executionFee, byte[] referralCode, string callbackTarget, CancellationTokenSource cancellationToken = null) + { + var createIncreasePositionFunction = new CreateIncreasePositionFunction(); + createIncreasePositionFunction.Path = path; + createIncreasePositionFunction.IndexToken = indexToken; + createIncreasePositionFunction.AmountIn = amountIn; + createIncreasePositionFunction.MinOut = minOut; + createIncreasePositionFunction.SizeDelta = sizeDelta; + createIncreasePositionFunction.IsLong = isLong; + createIncreasePositionFunction.AcceptablePrice = acceptablePrice; + createIncreasePositionFunction.ExecutionFee = executionFee; + createIncreasePositionFunction.ReferralCode = referralCode; + createIncreasePositionFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreasePositionFunction, cancellationToken); + } + + public Task CreateIncreasePositionETHRequestAsync(CreateIncreasePositionETHFunction createIncreasePositionETHFunction) + { + return ContractHandler.SendRequestAsync(createIncreasePositionETHFunction); + } + + public Task CreateIncreasePositionETHRequestAndWaitForReceiptAsync(CreateIncreasePositionETHFunction createIncreasePositionETHFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreasePositionETHFunction, cancellationToken); + } + + public Task CreateIncreasePositionETHRequestAsync(List path, string indexToken, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger acceptablePrice, BigInteger executionFee, byte[] referralCode, string callbackTarget) + { + var createIncreasePositionETHFunction = new CreateIncreasePositionETHFunction(); + createIncreasePositionETHFunction.Path = path; + createIncreasePositionETHFunction.IndexToken = indexToken; + createIncreasePositionETHFunction.MinOut = minOut; + createIncreasePositionETHFunction.SizeDelta = sizeDelta; + createIncreasePositionETHFunction.IsLong = isLong; + createIncreasePositionETHFunction.AcceptablePrice = acceptablePrice; + createIncreasePositionETHFunction.ExecutionFee = executionFee; + createIncreasePositionETHFunction.ReferralCode = referralCode; + createIncreasePositionETHFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAsync(createIncreasePositionETHFunction); + } + + public Task CreateIncreasePositionETHRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger acceptablePrice, BigInteger executionFee, byte[] referralCode, string callbackTarget, CancellationTokenSource cancellationToken = null) + { + var createIncreasePositionETHFunction = new CreateIncreasePositionETHFunction(); + createIncreasePositionETHFunction.Path = path; + createIncreasePositionETHFunction.IndexToken = indexToken; + createIncreasePositionETHFunction.MinOut = minOut; + createIncreasePositionETHFunction.SizeDelta = sizeDelta; + createIncreasePositionETHFunction.IsLong = isLong; + createIncreasePositionETHFunction.AcceptablePrice = acceptablePrice; + createIncreasePositionETHFunction.ExecutionFee = executionFee; + createIncreasePositionETHFunction.ReferralCode = referralCode; + createIncreasePositionETHFunction.CallbackTarget = callbackTarget; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(createIncreasePositionETHFunction, cancellationToken); + } + + public Task DecreasePositionRequestKeysQueryAsync(DecreasePositionRequestKeysFunction decreasePositionRequestKeysFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(decreasePositionRequestKeysFunction, blockParameter); + } + + + public Task DecreasePositionRequestKeysQueryAsync(BigInteger returnValue1, BlockParameter blockParameter = null) + { + var decreasePositionRequestKeysFunction = new DecreasePositionRequestKeysFunction(); + decreasePositionRequestKeysFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(decreasePositionRequestKeysFunction, blockParameter); + } + + public Task DecreasePositionRequestKeysStartQueryAsync(DecreasePositionRequestKeysStartFunction decreasePositionRequestKeysStartFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(decreasePositionRequestKeysStartFunction, blockParameter); + } + + + public Task DecreasePositionRequestKeysStartQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task DecreasePositionRequestsQueryAsync(DecreasePositionRequestsFunction decreasePositionRequestsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(decreasePositionRequestsFunction, blockParameter); + } + + public Task DecreasePositionRequestsQueryAsync(byte[] returnValue1, BlockParameter blockParameter = null) + { + var decreasePositionRequestsFunction = new DecreasePositionRequestsFunction(); + decreasePositionRequestsFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryDeserializingToObjectAsync(decreasePositionRequestsFunction, blockParameter); + } + + public Task DecreasePositionsIndexQueryAsync(DecreasePositionsIndexFunction decreasePositionsIndexFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(decreasePositionsIndexFunction, blockParameter); + } + + + public Task DecreasePositionsIndexQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var decreasePositionsIndexFunction = new DecreasePositionsIndexFunction(); + decreasePositionsIndexFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(decreasePositionsIndexFunction, blockParameter); + } + + public Task DepositFeeQueryAsync(DepositFeeFunction depositFeeFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(depositFeeFunction, blockParameter); + } + + + public Task DepositFeeQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task ExecuteDecreasePositionRequestAsync(ExecuteDecreasePositionFunction executeDecreasePositionFunction) + { + return ContractHandler.SendRequestAsync(executeDecreasePositionFunction); + } + + public Task ExecuteDecreasePositionRequestAndWaitForReceiptAsync(ExecuteDecreasePositionFunction executeDecreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreasePositionFunction, cancellationToken); + } + + public Task ExecuteDecreasePositionRequestAsync(byte[] key, string executionFeeReceiver) + { + var executeDecreasePositionFunction = new ExecuteDecreasePositionFunction(); + executeDecreasePositionFunction.Key = key; + executeDecreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(executeDecreasePositionFunction); + } + + public Task ExecuteDecreasePositionRequestAndWaitForReceiptAsync(byte[] key, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeDecreasePositionFunction = new ExecuteDecreasePositionFunction(); + executeDecreasePositionFunction.Key = key; + executeDecreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreasePositionFunction, cancellationToken); + } + + public Task ExecuteDecreasePositionsRequestAsync(ExecuteDecreasePositionsFunction executeDecreasePositionsFunction) + { + return ContractHandler.SendRequestAsync(executeDecreasePositionsFunction); + } + + public Task ExecuteDecreasePositionsRequestAndWaitForReceiptAsync(ExecuteDecreasePositionsFunction executeDecreasePositionsFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreasePositionsFunction, cancellationToken); + } + + public Task ExecuteDecreasePositionsRequestAsync(BigInteger endIndex, string executionFeeReceiver) + { + var executeDecreasePositionsFunction = new ExecuteDecreasePositionsFunction(); + executeDecreasePositionsFunction.EndIndex = endIndex; + executeDecreasePositionsFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(executeDecreasePositionsFunction); + } + + public Task ExecuteDecreasePositionsRequestAndWaitForReceiptAsync(BigInteger endIndex, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeDecreasePositionsFunction = new ExecuteDecreasePositionsFunction(); + executeDecreasePositionsFunction.EndIndex = endIndex; + executeDecreasePositionsFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeDecreasePositionsFunction, cancellationToken); + } + + public Task ExecuteIncreasePositionRequestAsync(ExecuteIncreasePositionFunction executeIncreasePositionFunction) + { + return ContractHandler.SendRequestAsync(executeIncreasePositionFunction); + } + + public Task ExecuteIncreasePositionRequestAndWaitForReceiptAsync(ExecuteIncreasePositionFunction executeIncreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreasePositionFunction, cancellationToken); + } + + public Task ExecuteIncreasePositionRequestAsync(byte[] key, string executionFeeReceiver) + { + var executeIncreasePositionFunction = new ExecuteIncreasePositionFunction(); + executeIncreasePositionFunction.Key = key; + executeIncreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(executeIncreasePositionFunction); + } + + public Task ExecuteIncreasePositionRequestAndWaitForReceiptAsync(byte[] key, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeIncreasePositionFunction = new ExecuteIncreasePositionFunction(); + executeIncreasePositionFunction.Key = key; + executeIncreasePositionFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreasePositionFunction, cancellationToken); + } + + public Task ExecuteIncreasePositionsRequestAsync(ExecuteIncreasePositionsFunction executeIncreasePositionsFunction) + { + return ContractHandler.SendRequestAsync(executeIncreasePositionsFunction); + } + + public Task ExecuteIncreasePositionsRequestAndWaitForReceiptAsync(ExecuteIncreasePositionsFunction executeIncreasePositionsFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreasePositionsFunction, cancellationToken); + } + + public Task ExecuteIncreasePositionsRequestAsync(BigInteger endIndex, string executionFeeReceiver) + { + var executeIncreasePositionsFunction = new ExecuteIncreasePositionsFunction(); + executeIncreasePositionsFunction.EndIndex = endIndex; + executeIncreasePositionsFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAsync(executeIncreasePositionsFunction); + } + + public Task ExecuteIncreasePositionsRequestAndWaitForReceiptAsync(BigInteger endIndex, string executionFeeReceiver, CancellationTokenSource cancellationToken = null) + { + var executeIncreasePositionsFunction = new ExecuteIncreasePositionsFunction(); + executeIncreasePositionsFunction.EndIndex = endIndex; + executeIncreasePositionsFunction.ExecutionFeeReceiver = executionFeeReceiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(executeIncreasePositionsFunction, cancellationToken); + } + + public Task FeeReservesQueryAsync(FeeReservesFunction feeReservesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(feeReservesFunction, blockParameter); + } + + + public Task FeeReservesQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var feeReservesFunction = new FeeReservesFunction(); + feeReservesFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(feeReservesFunction, blockParameter); + } + + public Task> GetDecreasePositionRequestPathQueryAsync(GetDecreasePositionRequestPathFunction getDecreasePositionRequestPathFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getDecreasePositionRequestPathFunction, blockParameter); + } + + + public Task> GetDecreasePositionRequestPathQueryAsync(byte[] key, BlockParameter blockParameter = null) + { + var getDecreasePositionRequestPathFunction = new GetDecreasePositionRequestPathFunction(); + getDecreasePositionRequestPathFunction.Key = key; + + return ContractHandler.QueryAsync>(getDecreasePositionRequestPathFunction, blockParameter); + } + + public Task> GetIncreasePositionRequestPathQueryAsync(GetIncreasePositionRequestPathFunction getIncreasePositionRequestPathFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getIncreasePositionRequestPathFunction, blockParameter); + } + + + public Task> GetIncreasePositionRequestPathQueryAsync(byte[] key, BlockParameter blockParameter = null) + { + var getIncreasePositionRequestPathFunction = new GetIncreasePositionRequestPathFunction(); + getIncreasePositionRequestPathFunction.Key = key; + + return ContractHandler.QueryAsync>(getIncreasePositionRequestPathFunction, blockParameter); + } + + public Task GetRequestKeyQueryAsync(GetRequestKeyFunction getRequestKeyFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(getRequestKeyFunction, blockParameter); + } + + + public Task GetRequestKeyQueryAsync(string account, BigInteger index, BlockParameter blockParameter = null) + { + var getRequestKeyFunction = new GetRequestKeyFunction(); + getRequestKeyFunction.Account = account; + getRequestKeyFunction.Index = index; + + return ContractHandler.QueryAsync(getRequestKeyFunction, blockParameter); + } + + public Task GetRequestQueueLengthsQueryAsync(GetRequestQueueLengthsFunction getRequestQueueLengthsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getRequestQueueLengthsFunction, blockParameter); + } + + public Task GetRequestQueueLengthsQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(null, blockParameter); + } + + public Task GovQueryAsync(GovFunction govFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(govFunction, blockParameter); + } + + + public Task GovQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IncreasePositionBufferBpsQueryAsync(IncreasePositionBufferBpsFunction increasePositionBufferBpsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(increasePositionBufferBpsFunction, blockParameter); + } + + + public Task IncreasePositionBufferBpsQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IncreasePositionRequestKeysQueryAsync(IncreasePositionRequestKeysFunction increasePositionRequestKeysFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(increasePositionRequestKeysFunction, blockParameter); + } + + + public Task IncreasePositionRequestKeysQueryAsync(BigInteger returnValue1, BlockParameter blockParameter = null) + { + var increasePositionRequestKeysFunction = new IncreasePositionRequestKeysFunction(); + increasePositionRequestKeysFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(increasePositionRequestKeysFunction, blockParameter); + } + + public Task IncreasePositionRequestKeysStartQueryAsync(IncreasePositionRequestKeysStartFunction increasePositionRequestKeysStartFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(increasePositionRequestKeysStartFunction, blockParameter); + } + + + public Task IncreasePositionRequestKeysStartQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IncreasePositionRequestsQueryAsync(IncreasePositionRequestsFunction increasePositionRequestsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(increasePositionRequestsFunction, blockParameter); + } + + public Task IncreasePositionRequestsQueryAsync(byte[] returnValue1, BlockParameter blockParameter = null) + { + var increasePositionRequestsFunction = new IncreasePositionRequestsFunction(); + increasePositionRequestsFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryDeserializingToObjectAsync(increasePositionRequestsFunction, blockParameter); + } + + public Task IncreasePositionsIndexQueryAsync(IncreasePositionsIndexFunction increasePositionsIndexFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(increasePositionsIndexFunction, blockParameter); + } + + + public Task IncreasePositionsIndexQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var increasePositionsIndexFunction = new IncreasePositionsIndexFunction(); + increasePositionsIndexFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(increasePositionsIndexFunction, blockParameter); + } + + public Task IsLeverageEnabledQueryAsync(IsLeverageEnabledFunction isLeverageEnabledFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(isLeverageEnabledFunction, blockParameter); + } + + + public Task IsLeverageEnabledQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IsPositionKeeperQueryAsync(IsPositionKeeperFunction isPositionKeeperFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(isPositionKeeperFunction, blockParameter); + } + + + public Task IsPositionKeeperQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var isPositionKeeperFunction = new IsPositionKeeperFunction(); + isPositionKeeperFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(isPositionKeeperFunction, blockParameter); + } + + public Task MaxGlobalLongSizesQueryAsync(MaxGlobalLongSizesFunction maxGlobalLongSizesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(maxGlobalLongSizesFunction, blockParameter); + } + + + public Task MaxGlobalLongSizesQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var maxGlobalLongSizesFunction = new MaxGlobalLongSizesFunction(); + maxGlobalLongSizesFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(maxGlobalLongSizesFunction, blockParameter); + } + + public Task MaxGlobalShortSizesQueryAsync(MaxGlobalShortSizesFunction maxGlobalShortSizesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(maxGlobalShortSizesFunction, blockParameter); + } + + + public Task MaxGlobalShortSizesQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var maxGlobalShortSizesFunction = new MaxGlobalShortSizesFunction(); + maxGlobalShortSizesFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(maxGlobalShortSizesFunction, blockParameter); + } + + public Task MaxTimeDelayQueryAsync(MaxTimeDelayFunction maxTimeDelayFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(maxTimeDelayFunction, blockParameter); + } + + + public Task MaxTimeDelayQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task MinBlockDelayKeeperQueryAsync(MinBlockDelayKeeperFunction minBlockDelayKeeperFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(minBlockDelayKeeperFunction, blockParameter); + } + + + public Task MinBlockDelayKeeperQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task MinExecutionFeeQueryAsync(MinExecutionFeeFunction minExecutionFeeFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(minExecutionFeeFunction, blockParameter); + } + + + public Task MinExecutionFeeQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task MinTimeDelayPublicQueryAsync(MinTimeDelayPublicFunction minTimeDelayPublicFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(minTimeDelayPublicFunction, blockParameter); + } + + + public Task MinTimeDelayPublicQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task ReferralStorageQueryAsync(ReferralStorageFunction referralStorageFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(referralStorageFunction, blockParameter); + } + + + public Task ReferralStorageQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task RouterQueryAsync(RouterFunction routerFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(routerFunction, blockParameter); + } + + + public Task RouterQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task SendValueRequestAsync(SendValueFunction sendValueFunction) + { + return ContractHandler.SendRequestAsync(sendValueFunction); + } + + public Task SendValueRequestAndWaitForReceiptAsync(SendValueFunction sendValueFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(sendValueFunction, cancellationToken); + } + + public Task SendValueRequestAsync(string receiver, BigInteger amount) + { + var sendValueFunction = new SendValueFunction(); + sendValueFunction.Receiver = receiver; + sendValueFunction.Amount = amount; + + return ContractHandler.SendRequestAsync(sendValueFunction); + } + + public Task SendValueRequestAndWaitForReceiptAsync(string receiver, BigInteger amount, CancellationTokenSource cancellationToken = null) + { + var sendValueFunction = new SendValueFunction(); + sendValueFunction.Receiver = receiver; + sendValueFunction.Amount = amount; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(sendValueFunction, cancellationToken); + } + + public Task SetAdminRequestAsync(SetAdminFunction setAdminFunction) + { + return ContractHandler.SendRequestAsync(setAdminFunction); + } + + public Task SetAdminRequestAndWaitForReceiptAsync(SetAdminFunction setAdminFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setAdminFunction, cancellationToken); + } + + public Task SetAdminRequestAsync(string admin) + { + var setAdminFunction = new SetAdminFunction(); + setAdminFunction.Admin = admin; + + return ContractHandler.SendRequestAsync(setAdminFunction); + } + + public Task SetAdminRequestAndWaitForReceiptAsync(string admin, CancellationTokenSource cancellationToken = null) + { + var setAdminFunction = new SetAdminFunction(); + setAdminFunction.Admin = admin; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setAdminFunction, cancellationToken); + } + + public Task SetCallbackGasLimitRequestAsync(SetCallbackGasLimitFunction setCallbackGasLimitFunction) + { + return ContractHandler.SendRequestAsync(setCallbackGasLimitFunction); + } + + public Task SetCallbackGasLimitRequestAndWaitForReceiptAsync(SetCallbackGasLimitFunction setCallbackGasLimitFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setCallbackGasLimitFunction, cancellationToken); + } + + public Task SetCallbackGasLimitRequestAsync(BigInteger callbackGasLimit) + { + var setCallbackGasLimitFunction = new SetCallbackGasLimitFunction(); + setCallbackGasLimitFunction.CallbackGasLimit = callbackGasLimit; + + return ContractHandler.SendRequestAsync(setCallbackGasLimitFunction); + } + + public Task SetCallbackGasLimitRequestAndWaitForReceiptAsync(BigInteger callbackGasLimit, CancellationTokenSource cancellationToken = null) + { + var setCallbackGasLimitFunction = new SetCallbackGasLimitFunction(); + setCallbackGasLimitFunction.CallbackGasLimit = callbackGasLimit; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setCallbackGasLimitFunction, cancellationToken); + } + + public Task SetDelayValuesRequestAsync(SetDelayValuesFunction setDelayValuesFunction) + { + return ContractHandler.SendRequestAsync(setDelayValuesFunction); + } + + public Task SetDelayValuesRequestAndWaitForReceiptAsync(SetDelayValuesFunction setDelayValuesFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setDelayValuesFunction, cancellationToken); + } + + public Task SetDelayValuesRequestAsync(BigInteger minBlockDelayKeeper, BigInteger minTimeDelayPublic, BigInteger maxTimeDelay) + { + var setDelayValuesFunction = new SetDelayValuesFunction(); + setDelayValuesFunction.MinBlockDelayKeeper = minBlockDelayKeeper; + setDelayValuesFunction.MinTimeDelayPublic = minTimeDelayPublic; + setDelayValuesFunction.MaxTimeDelay = maxTimeDelay; + + return ContractHandler.SendRequestAsync(setDelayValuesFunction); + } + + public Task SetDelayValuesRequestAndWaitForReceiptAsync(BigInteger minBlockDelayKeeper, BigInteger minTimeDelayPublic, BigInteger maxTimeDelay, CancellationTokenSource cancellationToken = null) + { + var setDelayValuesFunction = new SetDelayValuesFunction(); + setDelayValuesFunction.MinBlockDelayKeeper = minBlockDelayKeeper; + setDelayValuesFunction.MinTimeDelayPublic = minTimeDelayPublic; + setDelayValuesFunction.MaxTimeDelay = maxTimeDelay; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setDelayValuesFunction, cancellationToken); + } + + public Task SetDepositFeeRequestAsync(SetDepositFeeFunction setDepositFeeFunction) + { + return ContractHandler.SendRequestAsync(setDepositFeeFunction); + } + + public Task SetDepositFeeRequestAndWaitForReceiptAsync(SetDepositFeeFunction setDepositFeeFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setDepositFeeFunction, cancellationToken); + } + + public Task SetDepositFeeRequestAsync(BigInteger depositFee) + { + var setDepositFeeFunction = new SetDepositFeeFunction(); + setDepositFeeFunction.DepositFee = depositFee; + + return ContractHandler.SendRequestAsync(setDepositFeeFunction); + } + + public Task SetDepositFeeRequestAndWaitForReceiptAsync(BigInteger depositFee, CancellationTokenSource cancellationToken = null) + { + var setDepositFeeFunction = new SetDepositFeeFunction(); + setDepositFeeFunction.DepositFee = depositFee; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setDepositFeeFunction, cancellationToken); + } + + public Task SetGovRequestAsync(SetGovFunction setGovFunction) + { + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(SetGovFunction setGovFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetGovRequestAsync(string gov) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(string gov, CancellationTokenSource cancellationToken = null) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetIncreasePositionBufferBpsRequestAsync(SetIncreasePositionBufferBpsFunction setIncreasePositionBufferBpsFunction) + { + return ContractHandler.SendRequestAsync(setIncreasePositionBufferBpsFunction); + } + + public Task SetIncreasePositionBufferBpsRequestAndWaitForReceiptAsync(SetIncreasePositionBufferBpsFunction setIncreasePositionBufferBpsFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setIncreasePositionBufferBpsFunction, cancellationToken); + } + + public Task SetIncreasePositionBufferBpsRequestAsync(BigInteger increasePositionBufferBps) + { + var setIncreasePositionBufferBpsFunction = new SetIncreasePositionBufferBpsFunction(); + setIncreasePositionBufferBpsFunction.IncreasePositionBufferBps = increasePositionBufferBps; + + return ContractHandler.SendRequestAsync(setIncreasePositionBufferBpsFunction); + } + + public Task SetIncreasePositionBufferBpsRequestAndWaitForReceiptAsync(BigInteger increasePositionBufferBps, CancellationTokenSource cancellationToken = null) + { + var setIncreasePositionBufferBpsFunction = new SetIncreasePositionBufferBpsFunction(); + setIncreasePositionBufferBpsFunction.IncreasePositionBufferBps = increasePositionBufferBps; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setIncreasePositionBufferBpsFunction, cancellationToken); + } + + public Task SetIsLeverageEnabledRequestAsync(SetIsLeverageEnabledFunction setIsLeverageEnabledFunction) + { + return ContractHandler.SendRequestAsync(setIsLeverageEnabledFunction); + } + + public Task SetIsLeverageEnabledRequestAndWaitForReceiptAsync(SetIsLeverageEnabledFunction setIsLeverageEnabledFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setIsLeverageEnabledFunction, cancellationToken); + } + + public Task SetIsLeverageEnabledRequestAsync(bool isLeverageEnabled) + { + var setIsLeverageEnabledFunction = new SetIsLeverageEnabledFunction(); + setIsLeverageEnabledFunction.IsLeverageEnabled = isLeverageEnabled; + + return ContractHandler.SendRequestAsync(setIsLeverageEnabledFunction); + } + + public Task SetIsLeverageEnabledRequestAndWaitForReceiptAsync(bool isLeverageEnabled, CancellationTokenSource cancellationToken = null) + { + var setIsLeverageEnabledFunction = new SetIsLeverageEnabledFunction(); + setIsLeverageEnabledFunction.IsLeverageEnabled = isLeverageEnabled; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setIsLeverageEnabledFunction, cancellationToken); + } + + public Task SetMaxGlobalSizesRequestAsync(SetMaxGlobalSizesFunction setMaxGlobalSizesFunction) + { + return ContractHandler.SendRequestAsync(setMaxGlobalSizesFunction); + } + + public Task SetMaxGlobalSizesRequestAndWaitForReceiptAsync(SetMaxGlobalSizesFunction setMaxGlobalSizesFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMaxGlobalSizesFunction, cancellationToken); + } + + public Task SetMaxGlobalSizesRequestAsync(List tokens, List longSizes, List shortSizes) + { + var setMaxGlobalSizesFunction = new SetMaxGlobalSizesFunction(); + setMaxGlobalSizesFunction.Tokens = tokens; + setMaxGlobalSizesFunction.LongSizes = longSizes; + setMaxGlobalSizesFunction.ShortSizes = shortSizes; + + return ContractHandler.SendRequestAsync(setMaxGlobalSizesFunction); + } + + public Task SetMaxGlobalSizesRequestAndWaitForReceiptAsync(List tokens, List longSizes, List shortSizes, CancellationTokenSource cancellationToken = null) + { + var setMaxGlobalSizesFunction = new SetMaxGlobalSizesFunction(); + setMaxGlobalSizesFunction.Tokens = tokens; + setMaxGlobalSizesFunction.LongSizes = longSizes; + setMaxGlobalSizesFunction.ShortSizes = shortSizes; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMaxGlobalSizesFunction, cancellationToken); + } + + public Task SetMinExecutionFeeRequestAsync(SetMinExecutionFeeFunction setMinExecutionFeeFunction) + { + return ContractHandler.SendRequestAsync(setMinExecutionFeeFunction); + } + + public Task SetMinExecutionFeeRequestAndWaitForReceiptAsync(SetMinExecutionFeeFunction setMinExecutionFeeFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinExecutionFeeFunction, cancellationToken); + } + + public Task SetMinExecutionFeeRequestAsync(BigInteger minExecutionFee) + { + var setMinExecutionFeeFunction = new SetMinExecutionFeeFunction(); + setMinExecutionFeeFunction.MinExecutionFee = minExecutionFee; + + return ContractHandler.SendRequestAsync(setMinExecutionFeeFunction); + } + + public Task SetMinExecutionFeeRequestAndWaitForReceiptAsync(BigInteger minExecutionFee, CancellationTokenSource cancellationToken = null) + { + var setMinExecutionFeeFunction = new SetMinExecutionFeeFunction(); + setMinExecutionFeeFunction.MinExecutionFee = minExecutionFee; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setMinExecutionFeeFunction, cancellationToken); + } + + public Task SetPositionKeeperRequestAsync(SetPositionKeeperFunction setPositionKeeperFunction) + { + return ContractHandler.SendRequestAsync(setPositionKeeperFunction); + } + + public Task SetPositionKeeperRequestAndWaitForReceiptAsync(SetPositionKeeperFunction setPositionKeeperFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setPositionKeeperFunction, cancellationToken); + } + + public Task SetPositionKeeperRequestAsync(string account, bool isActive) + { + var setPositionKeeperFunction = new SetPositionKeeperFunction(); + setPositionKeeperFunction.Account = account; + setPositionKeeperFunction.IsActive = isActive; + + return ContractHandler.SendRequestAsync(setPositionKeeperFunction); + } + + public Task SetPositionKeeperRequestAndWaitForReceiptAsync(string account, bool isActive, CancellationTokenSource cancellationToken = null) + { + var setPositionKeeperFunction = new SetPositionKeeperFunction(); + setPositionKeeperFunction.Account = account; + setPositionKeeperFunction.IsActive = isActive; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setPositionKeeperFunction, cancellationToken); + } + + public Task SetReferralStorageRequestAsync(SetReferralStorageFunction setReferralStorageFunction) + { + return ContractHandler.SendRequestAsync(setReferralStorageFunction); + } + + public Task SetReferralStorageRequestAndWaitForReceiptAsync(SetReferralStorageFunction setReferralStorageFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setReferralStorageFunction, cancellationToken); + } + + public Task SetReferralStorageRequestAsync(string referralStorage) + { + var setReferralStorageFunction = new SetReferralStorageFunction(); + setReferralStorageFunction.ReferralStorage = referralStorage; + + return ContractHandler.SendRequestAsync(setReferralStorageFunction); + } + + public Task SetReferralStorageRequestAndWaitForReceiptAsync(string referralStorage, CancellationTokenSource cancellationToken = null) + { + var setReferralStorageFunction = new SetReferralStorageFunction(); + setReferralStorageFunction.ReferralStorage = referralStorage; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setReferralStorageFunction, cancellationToken); + } + + public Task SetRequestKeysStartValuesRequestAsync(SetRequestKeysStartValuesFunction setRequestKeysStartValuesFunction) + { + return ContractHandler.SendRequestAsync(setRequestKeysStartValuesFunction); + } + + public Task SetRequestKeysStartValuesRequestAndWaitForReceiptAsync(SetRequestKeysStartValuesFunction setRequestKeysStartValuesFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setRequestKeysStartValuesFunction, cancellationToken); + } + + public Task SetRequestKeysStartValuesRequestAsync(BigInteger increasePositionRequestKeysStart, BigInteger decreasePositionRequestKeysStart) + { + var setRequestKeysStartValuesFunction = new SetRequestKeysStartValuesFunction(); + setRequestKeysStartValuesFunction.IncreasePositionRequestKeysStart = increasePositionRequestKeysStart; + setRequestKeysStartValuesFunction.DecreasePositionRequestKeysStart = decreasePositionRequestKeysStart; + + return ContractHandler.SendRequestAsync(setRequestKeysStartValuesFunction); + } + + public Task SetRequestKeysStartValuesRequestAndWaitForReceiptAsync(BigInteger increasePositionRequestKeysStart, BigInteger decreasePositionRequestKeysStart, CancellationTokenSource cancellationToken = null) + { + var setRequestKeysStartValuesFunction = new SetRequestKeysStartValuesFunction(); + setRequestKeysStartValuesFunction.IncreasePositionRequestKeysStart = increasePositionRequestKeysStart; + setRequestKeysStartValuesFunction.DecreasePositionRequestKeysStart = decreasePositionRequestKeysStart; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setRequestKeysStartValuesFunction, cancellationToken); + } + + public Task ShortsTrackerQueryAsync(ShortsTrackerFunction shortsTrackerFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(shortsTrackerFunction, blockParameter); + } + + + public Task ShortsTrackerQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task VaultQueryAsync(VaultFunction vaultFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(vaultFunction, blockParameter); + } + + + public Task VaultQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task WethQueryAsync(WethFunction wethFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(wethFunction, blockParameter); + } + + + public Task WethQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task WithdrawFeesRequestAsync(WithdrawFeesFunction withdrawFeesFunction) + { + return ContractHandler.SendRequestAsync(withdrawFeesFunction); + } + + public Task WithdrawFeesRequestAndWaitForReceiptAsync(WithdrawFeesFunction withdrawFeesFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(withdrawFeesFunction, cancellationToken); + } + + public Task WithdrawFeesRequestAsync(string token, string receiver) + { + var withdrawFeesFunction = new WithdrawFeesFunction(); + withdrawFeesFunction.Token = token; + withdrawFeesFunction.Receiver = receiver; + + return ContractHandler.SendRequestAsync(withdrawFeesFunction); + } + + public Task WithdrawFeesRequestAndWaitForReceiptAsync(string token, string receiver, CancellationTokenSource cancellationToken = null) + { + var withdrawFeesFunction = new WithdrawFeesFunction(); + withdrawFeesFunction.Token = token; + withdrawFeesFunction.Receiver = receiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(withdrawFeesFunction, cancellationToken); + } + } +} diff --git a/src/Managing.Tools.ABI/Program.cs b/src/Managing.Tools.ABI/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/src/Managing.Tools.ABI/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/src/Managing.Tools.ABI/Reader/ContractDefinition/ReaderDefinition.cs b/src/Managing.Tools.ABI/Reader/ContractDefinition/ReaderDefinition.cs new file mode 100644 index 0000000..3a7117f --- /dev/null +++ b/src/Managing.Tools.ABI/Reader/ContractDefinition/ReaderDefinition.cs @@ -0,0 +1,540 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; + +namespace Managing.Tools.Reader.ContractDefinition +{ + + + public partial class ReaderDeployment : ReaderDeploymentBase + { + public ReaderDeployment() : base(BYTECODE) { } + public ReaderDeployment(string byteCode) : base(byteCode) { } + } + + public class ReaderDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "608060405234801561001057600080fd5b50600080546001600160a01b031916331790556159ff80620000336000396000f3fe608060405234801561001057600080fd5b506004361061013e5760003560e01c8063126082cf1461014357806312d43a511461015d57806320542568146101815780632413c8c1146102925780632ac0184c146103335780632e3e3342146103e45780633613d52714610495578063440f018c1461054657806348211934146105a05780637b906e931461065157806386d4d0f514610712578063870d917c146107c35780638e83ca32146107cb578063937a0be81461088c57806395082d251461093d57806395a7535a146109455780639b0183c3146109ff578063a4543ead14610a20578063ad7e549714610ad1578063c6f1d67614610ad9578063cfad57a214610af5578063d7176ca914610b1b578063d802178e14610b70578063dc383cab14610c21578063f3535e6c14610ddf578063fbdb05ca14610e17575b600080fd5b61014b610ec8565b60408051918252519081900360200190f35b610165610ece565b604080516001600160a01b039092168252519081900360200190f35b6102426004803603608081101561019757600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156101d157600080fd5b8201836020820111156101e357600080fd5b803590602001918460208302840111600160201b8311171561020457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610edd945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561027e578181015183820152602001610266565b505050509050019250505060405180910390f35b610242600480360360208110156102a857600080fd5b810190602081018135600160201b8111156102c257600080fd5b8201836020820111156102d457600080fd5b803590602001918460208302840111600160201b831117156102f557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506115eb945050505050565b61014b6004803603604081101561034957600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561037357600080fd5b82018360208201111561038557600080fd5b803590602001918460208302840111600160201b831117156103a657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506116e4945050505050565b610242600480360360408110156103fa57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561042457600080fd5b82018360208201111561043657600080fd5b803590602001918460208302840111600160201b8311171561045757600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061180f945050505050565b610242600480360360408110156104ab57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104d557600080fd5b8201836020820111156104e757600080fd5b803590602001918460208302840111600160201b8311171561050857600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611a14945050505050565b6105826004803603608081101561055c57600080fd5b506001600160a01b03813581169160208101358216916040820135169060600135611e4e565b60408051938452602084019290925282820152519081900360600190f35b610242600480360360408110156105b657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156105e057600080fd5b8201836020820111156105f257600080fd5b803590602001918460208302840111600160201b8311171561061357600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506123b3945050505050565b6102426004803603608081101561066757600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156106a157600080fd5b8201836020820111156106b357600080fd5b803590602001918460208302840111600160201b831117156106d457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550612867945050505050565b6102426004803603604081101561072857600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561075257600080fd5b82018360208201111561076457600080fd5b803590602001918460208302840111600160201b8311171561078557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613098945050505050565b61014b613198565b610242600480360360808110156107e157600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b81111561081b57600080fd5b82018360208201111561082d57600080fd5b803590602001918460208302840111600160201b8311171561084e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061319d945050505050565b610242600480360360408110156108a257600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156108cc57600080fd5b8201836020820111156108de57600080fd5b803590602001918460208302840111600160201b831117156108ff57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613b1a945050505050565b61014b613caf565b6102426004803603606081101561095b57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561098e57600080fd5b8201836020820111156109a057600080fd5b803590602001918460208302840111600160201b831117156109c157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613cbf945050505050565b610a1e60048036036020811015610a1557600080fd5b5035151561418d565b005b61024260048036036040811015610a3657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610a6057600080fd5b820183602082011115610a7257600080fd5b803590602001918460208302840111600160201b83111715610a9357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614202945050505050565b61014b61446e565b610ae1614473565b604080519115158252519081900360200190f35b610a1e60048036036020811015610b0b57600080fd5b50356001600160a01b0316614483565b610b5760048036036080811015610b3157600080fd5b506001600160a01b038135811691602081013582169160408201351690606001356144fc565b6040805192835260208301919091528051918290030190f35b61024260048036036040811015610b8657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610bb057600080fd5b820183602082011115610bc257600080fd5b803590602001918460208302840111600160201b83111715610be357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614bb2945050505050565b610242600480360360a0811015610c3757600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b811115610c6a57600080fd5b820183602082011115610c7c57600080fd5b803590602001918460208302840111600160201b83111715610c9d57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610cec57600080fd5b820183602082011115610cfe57600080fd5b803590602001918460208302840111600160201b83111715610d1f57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b811115610d6e57600080fd5b820183602082011115610d8057600080fd5b803590602001918460208302840111600160201b83111715610da157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550614cfe945050505050565b61014b60048036036060811015610df557600080fd5b506001600160a01b0381358116916020810135821691604090910135166150fa565b61014b60048036036040811015610e2d57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610e5757600080fd5b820183602082011115610e6957600080fd5b803590602001918460208302840111600160201b83111715610e8a57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506156c1945050505050565b61271081565b6000546001600160a01b031681565b60606000600a905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f2557600080fd5b505afa158015610f39573d6000803e3d6000fd5b505050506040513d6020811015610f4f57600080fd5b5051855190915060609084026001600160401b0381118015610f7057600080fd5b50604051908082528060200260200182016040528015610f9a578160200160208202803683370190505b50905060005b86518110156115de576000878281518110610fb757fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415610fdf5750885b846001600160a01b03166352f55eed826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561102c57600080fd5b505afa158015611040573d6000803e3d6000fd5b505050506040513d602081101561105657600080fd5b50518351849084890290811061106857fe5b602002602001018181525050846001600160a01b031663c3c7b9e9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156110c157600080fd5b505afa1580156110d5573d6000803e3d6000fd5b505050506040513d60208110156110eb57600080fd5b5051835184906001858a020190811061110057fe5b602002602001018181525050846001600160a01b0316631aa4ace5826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561115957600080fd5b505afa15801561116d573d6000803e3d6000fd5b505050506040513d602081101561118357600080fd5b5051835184906002858a020190811061119857fe5b602002602001018181525050846001600160a01b0316632c668ec1828b6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b1580156111f957600080fd5b505afa15801561120d573d6000803e3d6000fd5b505050506040513d602081101561122357600080fd5b5051835184906003858a020190811061123857fe5b602002602001018181525050846001600160a01b031663ab2f3ad4826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561129157600080fd5b505afa1580156112a5573d6000803e3d6000fd5b505050506040513d60208110156112bb57600080fd5b5051835184906004858a02019081106112d057fe5b602002602001018181525050846001600160a01b03166381a612d6826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561132957600080fd5b505afa15801561133d573d6000803e3d6000fd5b505050506040513d602081101561135357600080fd5b5051835184906005858a020190811061136857fe5b602002602001018181525050846001600160a01b031663e124e6d2826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156113c157600080fd5b505afa1580156113d5573d6000803e3d6000fd5b505050506040513d60208110156113eb57600080fd5b5051835184906006858a020190811061140057fe5b602002602001018181525050846001600160a01b031663f07456ce826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561145957600080fd5b505afa15801561146d573d6000803e3d6000fd5b505050506040513d602081101561148357600080fd5b5051835184906007858a020190811061149857fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918716926356c8c2c192604480840193829003018186803b1580156114f257600080fd5b505afa158015611506573d6000803e3d6000fd5b505050506040513d602081101561151c57600080fd5b5051835184906008858a020190811061153157fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918716926356c8c2c192604480840193829003018186803b15801561158b57600080fd5b505afa15801561159f573d6000803e3d6000fd5b505050506040513d60208110156115b557600080fd5b5051835184906009858a02019081106115ca57fe5b602090810291909101015250600101610fa0565b5098975050505050505050565b60608082516001600160401b038111801561160557600080fd5b5060405190808252806020026020018201604052801561162f578160200160208202803683370190505b50905060005b83518110156116dd57600084828151811061164c57fe5b60200260200101519050806001600160a01b031663817b1cd26040518163ffffffff1660e01b815260040160206040518083038186803b15801561168f57600080fd5b505afa1580156116a3573d6000803e3d6000fd5b505050506040513d60208110156116b957600080fd5b505183518490849081106116c957fe5b602090810291909101015250600101611635565b5092915050565b600080836001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561172057600080fd5b505afa158015611734573d6000803e3d6000fd5b505050506040513d602081101561174a57600080fd5b5051905060005b835181101561180557600084828151811061176857fe5b602002602001015190506000866001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156117c157600080fd5b505afa1580156117d5573d6000803e3d6000fd5b505050506040513d60208110156117eb57600080fd5b505190506117f9848261577a565b93505050600101611751565b5090505b92915050565b60606000600290506060818451026001600160401b038111801561183257600080fd5b5060405190808252806020026020018201604052801561185c578160200160208202803683370190505b50905060005b8451811015611a0b57600085828151811061187957fe5b6020026020010151905060006001600160a01b0316816001600160a01b031614156118e857866001600160a01b03163183858402815181106118b757fe5b602002602001018181525050600083858402600101815181106118d657fe5b60200260200101818152505050611a03565b806001600160a01b03166370a08231886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561193557600080fd5b505afa158015611949573d6000803e3d6000fd5b505050506040513d602081101561195f57600080fd5b50518351849084870290811061197157fe5b602002602001018181525050806001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156119b657600080fd5b505afa1580156119ca573d6000803e3d6000fd5b505050506040513d60208110156119e057600080fd5b5051835184906001858802019081106119f557fe5b602002602001018181525050505b600101611862565b50949350505050565b60606000600690506060818451026001600160401b0381118015611a3757600080fd5b50604051908082528060200260200182016040528015611a61578160200160208202803683370190505b50905060005b8451811015611a0b576000858281518110611a7e57fe5b602090810291909101810151604080516317e1d38560e11b81526001600160a01b0380841660048301526001602483018190526044830152600060648301529151929450908a1692632fc3a70a92608480840193829003018186803b158015611ae657600080fd5b505afa158015611afa573d6000803e3d6000fd5b505050506040513d6020811015611b1057600080fd5b505183518490848702908110611b2257fe5b602090810291909101810191909152604080516317e1d38560e11b81526001600160a01b0384811660048301526000602483018190526001604484015260648301529151918a1692632fc3a70a92608480840193829003018186803b158015611b8a57600080fd5b505afa158015611b9e573d6000803e3d6000fd5b505050506040513d6020811015611bb457600080fd5b505183518490600185880201908110611bc957fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918a16926356c8c2c192604480840193829003018186803b158015611c2357600080fd5b505afa158015611c37573d6000803e3d6000fd5b505050506040513d6020811015611c4d57600080fd5b505183518490600285880201908110611c6257fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918a16926356c8c2c192604480840193829003018186803b158015611cbc57600080fd5b505afa158015611cd0573d6000803e3d6000fd5b505050506040513d6020811015611ce657600080fd5b505183518490600385880201908110611cfb57fe5b602002602001018181525050866001600160a01b0316636ce8a44b826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611d5457600080fd5b505afa158015611d68573d6000803e3d6000fd5b505050506040513d6020811015611d7e57600080fd5b5051611d8b576000611d8e565b60015b60ff168385840260040181518110611da257fe5b602002602001018181525050866001600160a01b03166348cac277826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611dfb57600080fd5b505afa158015611e0f573d6000803e3d6000fd5b505050506040513d6020811015611e2557600080fd5b505183518490600585880201908110611e3a57fe5b602090810291909101015250600101611a67565b600080600080876001600160a01b03166381a612d6886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611ea157600080fd5b505afa158015611eb5573d6000803e3d6000fd5b505050506040513d6020811015611ecb57600080fd5b5051604080516323b95ceb60e21b81526001600160a01b038a811660048301529151929350600092918b1691638ee573ac91602480820192602092909190829003018186803b158015611f1d57600080fd5b505afa158015611f31573d6000803e3d6000fd5b505050506040513d6020811015611f4757600080fd5b505190506000611f6d68327cb2734119d3b7a9601e1b611f6789866157bc565b90615815565b9050611f89600a83900a611f6783670de0b6b3a76400006157bc565b905060008a6001600160a01b03166342b60b038b6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015611fda57600080fd5b505afa158015611fee573d6000803e3d6000fd5b505050506040513d602081101561200457600080fd5b5051801561208757508a6001600160a01b03166342b60b038a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561205a57600080fd5b505afa15801561206e573d6000803e3d6000fd5b505050506040513d602081101561208457600080fd5b50515b90506000816120fa578b6001600160a01b031663a22f23926040518163ffffffff1660e01b815260040160206040518083038186803b1580156120c957600080fd5b505afa1580156120dd573d6000803e3d6000fd5b505050506040513d60208110156120f357600080fd5b5051612160565b8b6001600160a01b031663df73a2676040518163ffffffff1660e01b815260040160206040518083038186803b15801561213357600080fd5b505afa158015612147573d6000803e3d6000fd5b505050506040513d602081101561215d57600080fd5b50515b90506000826121d3578c6001600160a01b0316637a210a2b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156121a257600080fd5b505afa1580156121b6573d6000803e3d6000fd5b505050506040513d60208110156121cc57600080fd5b5051612239565b8c6001600160a01b03166310eb56c26040518163ffffffff1660e01b815260040160206040518083038186803b15801561220c57600080fd5b505afa158015612220573d6000803e3d6000fd5b505050506040513d602081101561223657600080fd5b50515b905060008d6001600160a01b031663c7e074c38e87868660016040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b1580156122ad57600080fd5b505afa1580156122c1573d6000803e3d6000fd5b505050506040513d60208110156122d757600080fd5b8101908080519060200190929190505050905060008e6001600160a01b031663c7e074c38e88878760006040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b15801561235c57600080fd5b505afa158015612370573d6000803e3d6000fd5b505050506040513d602081101561238657600080fd5b505190506000818311612399578161239b565b825b9b509199509750505050505050509450945094915050565b60606000600790506060818451026001600160401b03811180156123d657600080fd5b50604051908082528060200260200182016040528015612400578160200160208202803683370190505b50905060005b8451811015611a0b57600085828151811061241d57fe5b60200260200101519050806001600160a01b0316635d50e729886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561247457600080fd5b505afa158015612488573d6000803e3d6000fd5b505050506040513d602081101561249e57600080fd5b5051835184908487029081106124b057fe5b602002602001018181525050806001600160a01b031663d5a73fdd886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561250957600080fd5b505afa15801561251d573d6000803e3d6000fd5b505050506040513d602081101561253357600080fd5b50518351849060018588020190811061254857fe5b60200260200101818152505085828151811061256057fe5b60200260200101516001600160a01b03166370a08231886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156125b457600080fd5b505afa1580156125c8573d6000803e3d6000fd5b505050506040513d60208110156125de57600080fd5b5051835184906002858802019081106125f357fe5b602002602001018181525050806001600160a01b03166371417b32886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561264c57600080fd5b505afa158015612660573d6000803e3d6000fd5b505050506040513d602081101561267657600080fd5b50518351849060038588020190811061268b57fe5b602002602001018181525050806001600160a01b031663402914f5886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156126e457600080fd5b505afa1580156126f8573d6000803e3d6000fd5b505050506040513d602081101561270e57600080fd5b50518351849060048588020190811061272357fe5b602002602001018181525050806001600160a01b03166308f26c76886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561277c57600080fd5b505afa158015612790573d6000803e3d6000fd5b505050506040513d60208110156127a657600080fd5b5051835184906005858802019081106127bb57fe5b602002602001018181525050806001600160a01b03166345f01ee6886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561281457600080fd5b505afa158015612828573d6000803e3d6000fd5b505050506040513d602081101561283e57600080fd5b50518351849060068588020190811061285357fe5b602090810291909101015250600101612406565b60606000600c905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b1580156128af57600080fd5b505afa1580156128c3573d6000803e3d6000fd5b505050506040513d60208110156128d957600080fd5b5051855190915060609084026001600160401b03811180156128fa57600080fd5b50604051908082528060200260200182016040528015612924578160200160208202803683370190505b50905060005b86518110156115de57600087828151811061294157fe5b6020026020010151905060006001600160a01b0316816001600160a01b031614156129695750885b846001600160a01b03166352f55eed826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156129b657600080fd5b505afa1580156129ca573d6000803e3d6000fd5b505050506040513d60208110156129e057600080fd5b5051835184908489029081106129f257fe5b602002602001018181525050846001600160a01b031663c3c7b9e9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612a4b57600080fd5b505afa158015612a5f573d6000803e3d6000fd5b505050506040513d6020811015612a7557600080fd5b5051835184906001858a0201908110612a8a57fe5b602002602001018181525050846001600160a01b0316631aa4ace5826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612ae357600080fd5b505afa158015612af7573d6000803e3d6000fd5b505050506040513d6020811015612b0d57600080fd5b5051835184906002858a0201908110612b2257fe5b602002602001018181525050846001600160a01b0316632c668ec1828b6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b158015612b8357600080fd5b505afa158015612b97573d6000803e3d6000fd5b505050506040513d6020811015612bad57600080fd5b5051835184906003858a0201908110612bc257fe5b602002602001018181525050846001600160a01b031663ab2f3ad4826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612c1b57600080fd5b505afa158015612c2f573d6000803e3d6000fd5b505050506040513d6020811015612c4557600080fd5b5051835184906004858a0201908110612c5a57fe5b602002602001018181525050846001600160a01b0316634a993ee9826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612cb357600080fd5b505afa158015612cc7573d6000803e3d6000fd5b505050506040513d6020811015612cdd57600080fd5b5051835184906005858a0201908110612cf257fe5b602002602001018181525050846001600160a01b031663ad1e4f8d826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612d4b57600080fd5b505afa158015612d5f573d6000803e3d6000fd5b505050506040513d6020811015612d7557600080fd5b5051835184906006858a0201908110612d8a57fe5b602002602001018181525050846001600160a01b03166381a612d6826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612de357600080fd5b505afa158015612df7573d6000803e3d6000fd5b505050506040513d6020811015612e0d57600080fd5b5051835184906007858a0201908110612e2257fe5b602002602001018181525050846001600160a01b031663e124e6d2826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612e7b57600080fd5b505afa158015612e8f573d6000803e3d6000fd5b505050506040513d6020811015612ea557600080fd5b5051835184906008858a0201908110612eba57fe5b602002602001018181525050846001600160a01b031663f07456ce826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015612f1357600080fd5b505afa158015612f27573d6000803e3d6000fd5b505050506040513d6020811015612f3d57600080fd5b5051835184906009858a0201908110612f5257fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600060248301529151918716926356c8c2c192604480840193829003018186803b158015612fac57600080fd5b505afa158015612fc0573d6000803e3d6000fd5b505050506040513d6020811015612fd657600080fd5b505183518490600a858a0201908110612feb57fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038481166004830152600160248301529151918716926356c8c2c192604480840193829003018186803b15801561304557600080fd5b505afa158015613059573d6000803e3d6000fd5b505050506040513d602081101561306f57600080fd5b505183518490600b858a020190811061308457fe5b60209081029190910101525060010161292a565b60608082516001600160401b03811180156130b257600080fd5b506040519080825280602002602001820160405280156130dc578160200160208202803683370190505b50905060005b835181101561180557846001600160a01b0316631ce9cb8f85838151811061310657fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561314b57600080fd5b505afa15801561315f573d6000803e3d6000fd5b505050506040513d602081101561317557600080fd5b5051825183908390811061318557fe5b60209081029190910101526001016130e2565b601281565b60606000600e905060008690506000816001600160a01b031663741bef1a6040518163ffffffff1660e01b815260040160206040518083038186803b1580156131e557600080fd5b505afa1580156131f9573d6000803e3d6000fd5b505050506040513d602081101561320f57600080fd5b5051855190915060609084026001600160401b038111801561323057600080fd5b5060405190808252806020026020018201604052801561325a578160200160208202803683370190505b50905060005b86518110156115de57600087828151811061327757fe5b6020026020010151905060006001600160a01b0316816001600160a01b0316141561329f5750885b60008054600160a01b900460ff166132b8576000613332565b856001600160a01b0316639698d25a836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561330557600080fd5b505afa158015613319573d6000803e3d6000fd5b505050506040513d602081101561332f57600080fd5b50515b9050856001600160a01b03166352f55eed836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561338157600080fd5b505afa158015613395573d6000803e3d6000fd5b505050506040513d60208110156133ab57600080fd5b505184518590858a029081106133bd57fe5b602002602001018181525050856001600160a01b031663c3c7b9e9836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561341657600080fd5b505afa15801561342a573d6000803e3d6000fd5b505050506040513d602081101561344057600080fd5b5051845185906001868b020190811061345557fe5b602002602001018181525050856001600160a01b0316631aa4ace5836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156134ae57600080fd5b505afa1580156134c2573d6000803e3d6000fd5b505050506040513d60208110156134d857600080fd5b5051845185906002868b02019081106134ed57fe5b602002602001018181525050856001600160a01b0316632c668ec1838c6040518363ffffffff1660e01b815260040180836001600160a01b031681526020018281526020019250505060206040518083038186803b15801561354e57600080fd5b505afa158015613562573d6000803e3d6000fd5b505050506040513d602081101561357857600080fd5b5051845185906003868b020190811061358d57fe5b602002602001018181525050856001600160a01b031663ab2f3ad4836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156135e657600080fd5b505afa1580156135fa573d6000803e3d6000fd5b505050506040513d602081101561361057600080fd5b5051845185906004868b020190811061362557fe5b602002602001018181525050856001600160a01b0316634a993ee9836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561367e57600080fd5b505afa158015613692573d6000803e3d6000fd5b505050506040513d60208110156136a857600080fd5b5051845185906005868b02019081106136bd57fe5b602002602001018181525050856001600160a01b031663ad1e4f8d836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561371657600080fd5b505afa15801561372a573d6000803e3d6000fd5b505050506040513d602081101561374057600080fd5b5051845185906006868b020190811061375557fe5b602002602001018181525050856001600160a01b0316638a78daa8836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156137ae57600080fd5b505afa1580156137c2573d6000803e3d6000fd5b505050506040513d60208110156137d857600080fd5b5051845185906007868b02019081106137ed57fe5b60200260200101818152505080848885026008018151811061380b57fe5b602002602001018181525050856001600160a01b03166381a612d6836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561386457600080fd5b505afa158015613878573d6000803e3d6000fd5b505050506040513d602081101561388e57600080fd5b5051845185906009868b02019081106138a357fe5b602002602001018181525050856001600160a01b031663e124e6d2836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156138fc57600080fd5b505afa158015613910573d6000803e3d6000fd5b505050506040513d602081101561392657600080fd5b505184518590600a868b020190811061393b57fe5b602002602001018181525050856001600160a01b031663f07456ce836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561399457600080fd5b505afa1580156139a8573d6000803e3d6000fd5b505050506040513d60208110156139be57600080fd5b505184518590600b868b02019081106139d357fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038581166004830152600060248301529151918816926356c8c2c192604480840193829003018186803b158015613a2d57600080fd5b505afa158015613a41573d6000803e3d6000fd5b505050506040513d6020811015613a5757600080fd5b505184518590600c868b0201908110613a6c57fe5b602090810291909101810191909152604080516356c8c2c160e01b81526001600160a01b038581166004830152600160248301529151918816926356c8c2c192604480840193829003018186803b158015613ac657600080fd5b505afa158015613ada573d6000803e3d6000fd5b505050506040513d6020811015613af057600080fd5b505184518590600d868b0201908110613b0557fe5b60209081029190910101525050600101613260565b60606000600290506060818451026001600160401b0381118015613b3d57600080fd5b50604051908082528060200260200182016040528015613b67578160200160208202803683370190505b50905060005b8451811015611a0b576000858281518110613b8457fe5b60200260200101519050806001600160a01b031663402914f5886040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613bdb57600080fd5b505afa158015613bef573d6000803e3d6000fd5b505050506040513d6020811015613c0557600080fd5b505183518490848702908110613c1757fe5b602002602001018181525050806001600160a01b0316632459f51d6040518163ffffffff1660e01b815260040160206040518083038186803b158015613c5c57600080fd5b505afa158015613c70573d6000803e3d6000fd5b505050506040513d6020811015613c8657600080fd5b505183518490600185880201908110613c9b57fe5b602090810291909101015250600101613b6d565b68327cb2734119d3b7a9601e1b81565b60606000600290506060818451026001600160401b0381118015613ce257600080fd5b50604051908082528060200260200182016040528015613d0c578160200160208202803683370190505b5090508560005b855181101561417f576000868281518110613d2a57fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415613d525750865b6000836001600160a01b03166342b60b03836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613da157600080fd5b505afa158015613db5573d6000803e3d6000fd5b505050506040513d6020811015613dcb57600080fd5b5051613e3b57836001600160a01b031663c4f718bf6040518163ffffffff1660e01b815260040160206040518083038186803b158015613e0a57600080fd5b505afa158015613e1e573d6000803e3d6000fd5b505050506040513d6020811015613e3457600080fd5b5051613ea1565b836001600160a01b031663134ca63b6040518163ffffffff1660e01b815260040160206040518083038186803b158015613e7457600080fd5b505afa158015613e88573d6000803e3d6000fd5b505050506040513d6020811015613e9e57600080fd5b50515b90506000846001600160a01b031663c3c7b9e9846040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015613ef257600080fd5b505afa158015613f06573d6000803e3d6000fd5b505050506040513d6020811015613f1c57600080fd5b5051604080516352f55eed60e01b81526001600160a01b0386811660048301529151929350600092918816916352f55eed91602480820192602092909190829003018186803b158015613f6e57600080fd5b505afa158015613f82573d6000803e3d6000fd5b505050506040513d6020811015613f9857600080fd5b505190508015613fcb57613fb081611f6785856157bc565b8789870281518110613fbe57fe5b6020026020010181815250505b6000866001600160a01b031663c65bc7b1866040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561401a57600080fd5b505afa15801561402e573d6000803e3d6000fd5b505050506040513d602081101561404457600080fd5b5051111561416f576000866001600160a01b031663a93acac2866040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561409b57600080fd5b505afa1580156140af573d6000803e3d6000fd5b505050506040513d60208110156140c557600080fd5b50516040805163c65bc7b160e01b81526001600160a01b0388811660048301529151929350600092918a169163c65bc7b191602480820192602092909190829003018186803b15801561411757600080fd5b505afa15801561412b573d6000803e3d6000fd5b505050506040513d602081101561414157600080fd5b5051905061414f8183615854565b898b89026001018151811061416057fe5b60200260200101818152505050505b505060019092019150613d139050565b5090925050505b9392505050565b6000546001600160a01b031633146141e4576040805162461bcd60e51b815260206004820152601560248201527423b7bb32b93730b136329d103337b93134b23232b760591b604482015290519081900360640190fd5b60008054911515600160a01b0260ff60a01b19909216919091179055565b80516060906002908190839082900482026001600160401b038111801561422857600080fd5b50604051908082528060200260200182016040528015614252578160200160208202803683370190505b50905060005b8386518161426257fe5b04811015614464576000868583028151811061427a57fe5b602002602001015190506000878684026001018151811061429757fe5b602002602001015190506000896001600160a01b031663e6a4390584846040518363ffffffff1660e01b815260040180836001600160a01b03168152602001826001600160a01b031681526020019250505060206040518083038186803b15801561430157600080fd5b505afa158015614315573d6000803e3d6000fd5b505050506040513d602081101561432b57600080fd5b5051604080516370a0823160e01b81526001600160a01b0380841660048301529151929350908516916370a0823191602480820192602092909190829003018186803b15801561437a57600080fd5b505afa15801561438e573d6000803e3d6000fd5b505050506040513d60208110156143a457600080fd5b5051855186908689029081106143b657fe5b602002602001018181525050816001600160a01b03166370a08231826040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561440f57600080fd5b505afa158015614423573d6000803e3d6000fd5b505050506040513d602081101561443957600080fd5b5051855186906001878a020190811061444e57fe5b6020908102919091010152505050600101614258565b5095945050505050565b600981565b600054600160a01b900460ff1681565b6000546001600160a01b031633146144da576040805162461bcd60e51b815260206004820152601560248201527423b7bb32b93730b136329d103337b93134b23232b760591b604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000806000866001600160a01b03166381a612d6876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561454e57600080fd5b505afa158015614562573d6000803e3d6000fd5b505050506040513d602081101561457857600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0389811660048301529151929350600092918a1691638ee573ac91602480820192602092909190829003018186803b1580156145ca57600080fd5b505afa1580156145de573d6000803e3d6000fd5b505050506040513d60208110156145f457600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0389811660048301529151929350600092918b1691638ee573ac91602480820192602092909190829003018186803b15801561464657600080fd5b505afa15801561465a573d6000803e3d6000fd5b505050506040513d602081101561467057600080fd5b5051905060008061469168327cb2734119d3b7a9601e1b611f678a886157bc565b90506146ad600a85900a611f6783670de0b6b3a76400006157bc565b905060008b6001600160a01b03166342b60b038c6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156146fe57600080fd5b505afa158015614712573d6000803e3d6000fd5b505050506040513d602081101561472857600080fd5b505180156147ab57508b6001600160a01b03166342b60b038b6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561477e57600080fd5b505afa158015614792573d6000803e3d6000fd5b505050506040513d60208110156147a857600080fd5b50515b905060008161481e578c6001600160a01b031663a22f23926040518163ffffffff1660e01b815260040160206040518083038186803b1580156147ed57600080fd5b505afa158015614801573d6000803e3d6000fd5b505050506040513d602081101561481757600080fd5b5051614884565b8c6001600160a01b031663df73a2676040518163ffffffff1660e01b815260040160206040518083038186803b15801561485757600080fd5b505afa15801561486b573d6000803e3d6000fd5b505050506040513d602081101561488157600080fd5b50515b90506000826148f7578d6001600160a01b0316637a210a2b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156148c657600080fd5b505afa1580156148da573d6000803e3d6000fd5b505050506040513d60208110156148f057600080fd5b505161495d565b8d6001600160a01b03166310eb56c26040518163ffffffff1660e01b815260040160206040518083038186803b15801561493057600080fd5b505afa158015614944573d6000803e3d6000fd5b505050506040513d602081101561495a57600080fd5b50515b905060008e6001600160a01b031663c7e074c38f87868660016040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b1580156149d157600080fd5b505afa1580156149e5573d6000803e3d6000fd5b505050506040513d60208110156149fb57600080fd5b8101908080519060200190929190505050905060008f6001600160a01b031663c7e074c38f88878760006040518663ffffffff1660e01b815260040180866001600160a01b0316815260200185815260200184815260200183815260200182151581526020019550505050505060206040518083038186803b158015614a8057600080fd5b505afa158015614a94573d6000803e3d6000fd5b505050506040513d6020811015614aaa57600080fd5b50519050808211614abb5780614abd565b815b965050505050505060008a6001600160a01b031663e124e6d28a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015614b1457600080fd5b505afa158015614b28573d6000803e3d6000fd5b505050506040513d6020811015614b3e57600080fd5b505190506000614b5282611f678b896157bc565b9050614b7185600a0a611f6786600a0a846157bc90919063ffffffff16565b90506000614b8f612710611f67614b88828861577a565b85906157bc565b90506000614b9d838361577a565b919e919d50909b505050505050505050505050565b60608082516001600160401b0381118015614bcc57600080fd5b50604051908082528060200260200182016040528015614bf6578160200160208202803683370190505b50905060005b8351811015611805576000848281518110614c1357fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415614c6157856001600160a01b031631838381518110614c4f57fe5b60200260200101818152505050614cf6565b806001600160a01b03166370a08231876040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015614cae57600080fd5b505afa158015614cc2573d6000803e3d6000fd5b505050506040513d6020811015614cd857600080fd5b50518351849084908110614ce857fe5b602002602001018181525050505b600101614bfc565b60608060098551026001600160401b0381118015614d1b57600080fd5b50604051908082528060200260200182016040528015614d45578160200160208202803683370190505b50905060005b85518110156150ef5760008060008060008060008e6001600160a01b0316634a3f088d8f8f8b81518110614d7b57fe5b60200260200101518f8c81518110614d8f57fe5b60200260200101518f8d81518110614da357fe5b60200260200101516040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b03168152602001836001600160a01b0316815260200182151581526020019450505050506101006040518083038186803b158015614e1257600080fd5b505afa158015614e26573d6000803e3d6000fd5b505050506040513d610100811015614e3d57600080fd5b50805160208201516040830151606084015160a085015160c086015160e0909601518f51959d50939b509199509750955091935090915087908a9060098b02908110614e8557fe5b602002602001018181525050858960098a0260010181518110614ea457fe5b602002602001018181525050848960098a0260020181518110614ec357fe5b602002602001018181525050838960098a0260030181518110614ee257fe5b60200260200101818152505081614efa576000614efd565b60015b60ff168960098a0260040181518110614f1257fe5b602002602001018181525050828960098a0260050181518110614f3157fe5b602002602001018181525050808960098a0260060181518110614f5057fe5b602002602001018181525050505050505050506000826009830281518110614f7457fe5b602002602001015190506000836009840260020181518110614f9257fe5b602002602001015190506000846009850260060181518110614fb057fe5b6020026020010151905060008211156150e4576000808c6001600160a01b0316635c07eaab8b8881518110614fe157fe5b602002602001015187878d8b81518110614ff757fe5b6020026020010151886040518663ffffffff1660e01b815260040180866001600160a01b03168152602001858152602001848152602001831515815260200182815260200195505050505050604080518083038186803b15801561505a57600080fd5b505afa15801561506e573d6000803e3d6000fd5b505050506040513d604081101561508457600080fd5b50805160209091015190925090508161509e5760006150a1565b60015b60ff168760098802600701815181106150b657fe5b602002602001018181525050808760098802600801815181106150d557fe5b60200260200101818152505050505b505050600101614d4b565b509695505050505050565b600080846001600160a01b03166381a612d6856040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561514a57600080fd5b505afa15801561515e573d6000803e3d6000fd5b505050506040513d602081101561517457600080fd5b505160408051637092736960e11b81526001600160a01b03868116600483015291519293506000929188169163e124e6d291602480820192602092909190829003018186803b1580156151c657600080fd5b505afa1580156151da573d6000803e3d6000fd5b505050506040513d60208110156151f057600080fd5b5051604080516323b95ceb60e21b81526001600160a01b038881166004830152915192935060009291891691638ee573ac91602480820192602092909190829003018186803b15801561524257600080fd5b505afa158015615256573d6000803e3d6000fd5b505050506040513d602081101561526c57600080fd5b5051604080516323b95ceb60e21b81526001600160a01b0388811660048301529151929350600092918a1691638ee573ac91602480820192602092909190829003018186803b1580156152be57600080fd5b505afa1580156152d2573d6000803e3d6000fd5b505050506040513d60208110156152e857600080fd5b5051604080516352f55eed60e01b81526001600160a01b038981166004830152915192935060009283928c16916352f55eed916024808301926020929190829003018186803b15801561533a57600080fd5b505afa15801561534e573d6000803e3d6000fd5b505050506040513d602081101561536457600080fd5b50516040805163c3c7b9e960e01b81526001600160a01b038b811660048301529151929350600092918d169163c3c7b9e991602480820192602092909190829003018186803b1580156153b657600080fd5b505afa1580156153ca573d6000803e3d6000fd5b505050506040513d60208110156153e057600080fd5b505160408051634a993ee960e01b81526001600160a01b038c811660048301529151929350600092918e1691634a993ee991602480820192602092909190829003018186803b15801561543257600080fd5b505afa158015615446573d6000803e3d6000fd5b505050506040513d602081101561545c57600080fd5b50519050600081831161546f5781615471565b825b905083811061548c5760009950505050505050505050614186565b6000615498858361577a565b90506154c587600a0a611f678a600a0a6154bf8e611f678f886157bc90919063ffffffff16565b906157bc565b955050505050506000896001600160a01b031663ad1e4f8d8a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561551b57600080fd5b505afa15801561552f573d6000803e3d6000fd5b505050506040513d602081101561554557600080fd5b5051905080156115de57896001600160a01b0316631aa4ace58a6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561559c57600080fd5b505afa1580156155b0573d6000803e3d6000fd5b505050506040513d60208110156155c657600080fd5b50518110156155de5760009650505050505050614186565b60006156638b6001600160a01b0316631aa4ace58c6040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561563057600080fd5b505afa158015615644573d6000803e3d6000fd5b505050506040513d602081101561565a57600080fd5b5051839061577a565b905061567f670de0b6b3a7640000611f6783600a89900a6157bc565b905061569b87611f678368327cb2734119d3b7a9601e1b6157bc565b9050808311156156b357965061418695505050505050565b505098975050505050505050565b600080805b83518110156118055760008482815181106156dd57fe5b602002602001015190506000866001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561573657600080fd5b505afa15801561574a573d6000803e3d6000fd5b505050506040513d602081101561576057600080fd5b5051905061576e8482615854565b935050506001016156c6565b600061418683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506158ac565b6000826157cb57506000611809565b828202828482816157d857fe5b04146141865760405162461bcd60e51b81526004018080602001828103825260218152602001806159a96021913960400191505060405180910390fd5b600061418683836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250615943565b600082820183811015614186576040805162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b604482015290519081900360640190fd5b6000818484111561593b5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156159005781810151838201526020016158e8565b50505050905090810190601f16801561592d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600081836159925760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156159005781810151838201526020016158e8565b50600083858161599e57fe5b049594505050505056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a264697066735822122045b628c599a8154fc42d4309c34d7a311429d723c48990eab8da614a960534d364736f6c634300060c0033"; + public ReaderDeploymentBase() : base(BYTECODE) { } + public ReaderDeploymentBase(string byteCode) : base(byteCode) { } + + } + + public partial class BasisPointsDivisorFunction : BasisPointsDivisorFunctionBase { } + + [Function("BASIS_POINTS_DIVISOR", "uint256")] + public class BasisPointsDivisorFunctionBase : FunctionMessage + { + + } + + public partial class PositionPropsLengthFunction : PositionPropsLengthFunctionBase { } + + [Function("POSITION_PROPS_LENGTH", "uint256")] + public class PositionPropsLengthFunctionBase : FunctionMessage + { + + } + + public partial class PricePrecisionFunction : PricePrecisionFunctionBase { } + + [Function("PRICE_PRECISION", "uint256")] + public class PricePrecisionFunctionBase : FunctionMessage + { + + } + + public partial class UsdgDecimalsFunction : UsdgDecimalsFunctionBase { } + + [Function("USDG_DECIMALS", "uint256")] + public class UsdgDecimalsFunctionBase : FunctionMessage + { + + } + + public partial class GetAmountOutFunction : GetAmountOutFunctionBase { } + + [Function("getAmountOut", typeof(GetAmountOutOutputDTO))] + public class GetAmountOutFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_tokenIn", 2)] + public virtual string TokenIn { get; set; } + [Parameter("address", "_tokenOut", 3)] + public virtual string TokenOut { get; set; } + [Parameter("uint256", "_amountIn", 4)] + public virtual BigInteger AmountIn { get; set; } + } + + public partial class GetFeeBasisPointsFunction : GetFeeBasisPointsFunctionBase { } + + [Function("getFeeBasisPoints", typeof(GetFeeBasisPointsOutputDTO))] + public class GetFeeBasisPointsFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_tokenIn", 2)] + public virtual string TokenIn { get; set; } + [Parameter("address", "_tokenOut", 3)] + public virtual string TokenOut { get; set; } + [Parameter("uint256", "_amountIn", 4)] + public virtual BigInteger AmountIn { get; set; } + } + + public partial class GetFeesFunction : GetFeesFunctionBase { } + + [Function("getFees", "uint256[]")] + public class GetFeesFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address[]", "_tokens", 2)] + public virtual List Tokens { get; set; } + } + + public partial class GetFullVaultTokenInfoFunction : GetFullVaultTokenInfoFunctionBase { } + + [Function("getFullVaultTokenInfo", "uint256[]")] + public class GetFullVaultTokenInfoFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_weth", 2)] + public virtual string Weth { get; set; } + [Parameter("uint256", "_usdgAmount", 3)] + public virtual BigInteger UsdgAmount { get; set; } + [Parameter("address[]", "_tokens", 4)] + public virtual List Tokens { get; set; } + } + + public partial class GetFundingRatesFunction : GetFundingRatesFunctionBase { } + + [Function("getFundingRates", "uint256[]")] + public class GetFundingRatesFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_weth", 2)] + public virtual string Weth { get; set; } + [Parameter("address[]", "_tokens", 3)] + public virtual List Tokens { get; set; } + } + + public partial class GetMaxAmountInFunction : GetMaxAmountInFunctionBase { } + + [Function("getMaxAmountIn", "uint256")] + public class GetMaxAmountInFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_tokenIn", 2)] + public virtual string TokenIn { get; set; } + [Parameter("address", "_tokenOut", 3)] + public virtual string TokenOut { get; set; } + } + + public partial class GetPairInfoFunction : GetPairInfoFunctionBase { } + + [Function("getPairInfo", "uint256[]")] + public class GetPairInfoFunctionBase : FunctionMessage + { + [Parameter("address", "_factory", 1)] + public virtual string Factory { get; set; } + [Parameter("address[]", "_tokens", 2)] + public virtual List Tokens { get; set; } + } + + public partial class GetPositionsFunction : GetPositionsFunctionBase { } + + [Function("getPositions", "uint256[]")] + public class GetPositionsFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_account", 2)] + public virtual string Account { get; set; } + [Parameter("address[]", "_collateralTokens", 3)] + public virtual List CollateralTokens { get; set; } + [Parameter("address[]", "_indexTokens", 4)] + public virtual List IndexTokens { get; set; } + [Parameter("bool[]", "_isLong", 5)] + public virtual List IsLong { get; set; } + } + + public partial class GetPricesFunction : GetPricesFunctionBase { } + + [Function("getPrices", "uint256[]")] + public class GetPricesFunctionBase : FunctionMessage + { + [Parameter("address", "_priceFeed", 1)] + public virtual string PriceFeed { get; set; } + [Parameter("address[]", "_tokens", 2)] + public virtual List Tokens { get; set; } + } + + public partial class GetStakingInfoFunction : GetStakingInfoFunctionBase { } + + [Function("getStakingInfo", "uint256[]")] + public class GetStakingInfoFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address[]", "_yieldTrackers", 2)] + public virtual List YieldTrackers { get; set; } + } + + public partial class GetTokenBalancesFunction : GetTokenBalancesFunctionBase { } + + [Function("getTokenBalances", "uint256[]")] + public class GetTokenBalancesFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address[]", "_tokens", 2)] + public virtual List Tokens { get; set; } + } + + public partial class GetTokenBalancesWithSuppliesFunction : GetTokenBalancesWithSuppliesFunctionBase { } + + [Function("getTokenBalancesWithSupplies", "uint256[]")] + public class GetTokenBalancesWithSuppliesFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address[]", "_tokens", 2)] + public virtual List Tokens { get; set; } + } + + public partial class GetTokenSupplyFunction : GetTokenSupplyFunctionBase { } + + [Function("getTokenSupply", "uint256")] + public class GetTokenSupplyFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("address[]", "_excludedAccounts", 2)] + public virtual List ExcludedAccounts { get; set; } + } + + public partial class GetTotalBalanceFunction : GetTotalBalanceFunctionBase { } + + [Function("getTotalBalance", "uint256")] + public class GetTotalBalanceFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("address[]", "_accounts", 2)] + public virtual List Accounts { get; set; } + } + + public partial class GetTotalStakedFunction : GetTotalStakedFunctionBase { } + + [Function("getTotalStaked", "uint256[]")] + public class GetTotalStakedFunctionBase : FunctionMessage + { + [Parameter("address[]", "_yieldTokens", 1)] + public virtual List YieldTokens { get; set; } + } + + public partial class GetVaultTokenInfoFunction : GetVaultTokenInfoFunctionBase { } + + [Function("getVaultTokenInfo", "uint256[]")] + public class GetVaultTokenInfoFunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_weth", 2)] + public virtual string Weth { get; set; } + [Parameter("uint256", "_usdgAmount", 3)] + public virtual BigInteger UsdgAmount { get; set; } + [Parameter("address[]", "_tokens", 4)] + public virtual List Tokens { get; set; } + } + + public partial class GetVaultTokenInfoV2Function : GetVaultTokenInfoV2FunctionBase { } + + [Function("getVaultTokenInfoV2", "uint256[]")] + public class GetVaultTokenInfoV2FunctionBase : FunctionMessage + { + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_weth", 2)] + public virtual string Weth { get; set; } + [Parameter("uint256", "_usdgAmount", 3)] + public virtual BigInteger UsdgAmount { get; set; } + [Parameter("address[]", "_tokens", 4)] + public virtual List Tokens { get; set; } + } + + public partial class GetVestingInfoFunction : GetVestingInfoFunctionBase { } + + [Function("getVestingInfo", "uint256[]")] + public class GetVestingInfoFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address[]", "_vesters", 2)] + public virtual List Vesters { get; set; } + } + + public partial class GovFunction : GovFunctionBase { } + + [Function("gov", "address")] + public class GovFunctionBase : FunctionMessage + { + + } + + public partial class HasMaxGlobalShortSizesFunction : HasMaxGlobalShortSizesFunctionBase { } + + [Function("hasMaxGlobalShortSizes", "bool")] + public class HasMaxGlobalShortSizesFunctionBase : FunctionMessage + { + + } + + public partial class SetConfigFunction : SetConfigFunctionBase { } + + [Function("setConfig")] + public class SetConfigFunctionBase : FunctionMessage + { + [Parameter("bool", "_hasMaxGlobalShortSizes", 1)] + public virtual bool HasMaxGlobalShortSizes { get; set; } + } + + public partial class SetGovFunction : SetGovFunctionBase { } + + [Function("setGov")] + public class SetGovFunctionBase : FunctionMessage + { + [Parameter("address", "_gov", 1)] + public virtual string Gov { get; set; } + } + + public partial class BasisPointsDivisorOutputDTO : BasisPointsDivisorOutputDTOBase { } + + [FunctionOutput] + public class BasisPointsDivisorOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class PositionPropsLengthOutputDTO : PositionPropsLengthOutputDTOBase { } + + [FunctionOutput] + public class PositionPropsLengthOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class PricePrecisionOutputDTO : PricePrecisionOutputDTOBase { } + + [FunctionOutput] + public class PricePrecisionOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class UsdgDecimalsOutputDTO : UsdgDecimalsOutputDTOBase { } + + [FunctionOutput] + public class UsdgDecimalsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GetAmountOutOutputDTO : GetAmountOutOutputDTOBase { } + + [FunctionOutput] + public class GetAmountOutOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + } + + public partial class GetFeeBasisPointsOutputDTO : GetFeeBasisPointsOutputDTOBase { } + + [FunctionOutput] + public class GetFeeBasisPointsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + [Parameter("uint256", "", 2)] + public virtual BigInteger ReturnValue2 { get; set; } + [Parameter("uint256", "", 3)] + public virtual BigInteger ReturnValue3 { get; set; } + } + + public partial class GetFeesOutputDTO : GetFeesOutputDTOBase { } + + [FunctionOutput] + public class GetFeesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetFullVaultTokenInfoOutputDTO : GetFullVaultTokenInfoOutputDTOBase { } + + [FunctionOutput] + public class GetFullVaultTokenInfoOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetFundingRatesOutputDTO : GetFundingRatesOutputDTOBase { } + + [FunctionOutput] + public class GetFundingRatesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetMaxAmountInOutputDTO : GetMaxAmountInOutputDTOBase { } + + [FunctionOutput] + public class GetMaxAmountInOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GetPairInfoOutputDTO : GetPairInfoOutputDTOBase { } + + [FunctionOutput] + public class GetPairInfoOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetPositionsOutputDTO : GetPositionsOutputDTOBase { } + + [FunctionOutput] + public class GetPositionsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetPricesOutputDTO : GetPricesOutputDTOBase { } + + [FunctionOutput] + public class GetPricesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetStakingInfoOutputDTO : GetStakingInfoOutputDTOBase { } + + [FunctionOutput] + public class GetStakingInfoOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetTokenBalancesOutputDTO : GetTokenBalancesOutputDTOBase { } + + [FunctionOutput] + public class GetTokenBalancesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetTokenBalancesWithSuppliesOutputDTO : GetTokenBalancesWithSuppliesOutputDTOBase { } + + [FunctionOutput] + public class GetTokenBalancesWithSuppliesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetTokenSupplyOutputDTO : GetTokenSupplyOutputDTOBase { } + + [FunctionOutput] + public class GetTokenSupplyOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GetTotalBalanceOutputDTO : GetTotalBalanceOutputDTOBase { } + + [FunctionOutput] + public class GetTotalBalanceOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256", "", 1)] + public virtual BigInteger ReturnValue1 { get; set; } + } + + public partial class GetTotalStakedOutputDTO : GetTotalStakedOutputDTOBase { } + + [FunctionOutput] + public class GetTotalStakedOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetVaultTokenInfoOutputDTO : GetVaultTokenInfoOutputDTOBase { } + + [FunctionOutput] + public class GetVaultTokenInfoOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetVaultTokenInfoV2OutputDTO : GetVaultTokenInfoV2OutputDTOBase { } + + [FunctionOutput] + public class GetVaultTokenInfoV2OutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GetVestingInfoOutputDTO : GetVestingInfoOutputDTOBase { } + + [FunctionOutput] + public class GetVestingInfoOutputDTOBase : IFunctionOutputDTO + { + [Parameter("uint256[]", "", 1)] + public virtual List ReturnValue1 { get; set; } + } + + public partial class GovOutputDTO : GovOutputDTOBase { } + + [FunctionOutput] + public class GovOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class HasMaxGlobalShortSizesOutputDTO : HasMaxGlobalShortSizesOutputDTOBase { } + + [FunctionOutput] + public class HasMaxGlobalShortSizesOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + + + +} diff --git a/src/Managing.Tools.ABI/Reader/ReaderService.cs b/src/Managing.Tools.ABI/Reader/ReaderService.cs new file mode 100644 index 0000000..9b0349f --- /dev/null +++ b/src/Managing.Tools.ABI/Reader/ReaderService.cs @@ -0,0 +1,443 @@ +using System.Numerics; +using Nethereum.Web3; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Contracts.ContractHandlers; +using Managing.Tools.Reader.ContractDefinition; + +namespace Managing.Tools.Reader +{ + public partial class ReaderService + { + public static Task DeployContractAndWaitForReceiptAsync(Web3 web3, ReaderDeployment readerDeployment, CancellationTokenSource cancellationTokenSource = null) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAndWaitForReceiptAsync(readerDeployment, cancellationTokenSource); + } + + public static Task DeployContractAsync(Web3 web3, ReaderDeployment readerDeployment) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAsync(readerDeployment); + } + + public static async Task DeployContractAndGetServiceAsync(Web3 web3, ReaderDeployment readerDeployment, CancellationTokenSource cancellationTokenSource = null) + { + var receipt = await DeployContractAndWaitForReceiptAsync(web3, readerDeployment, cancellationTokenSource); + return new ReaderService(web3, receipt.ContractAddress); + } + + protected IWeb3 Web3 { get; } + + public ContractHandler ContractHandler { get; } + + public ReaderService(Web3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public ReaderService(IWeb3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public Task BasisPointsDivisorQueryAsync(BasisPointsDivisorFunction basisPointsDivisorFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(basisPointsDivisorFunction, blockParameter); + } + + + public Task BasisPointsDivisorQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task PositionPropsLengthQueryAsync(PositionPropsLengthFunction positionPropsLengthFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(positionPropsLengthFunction, blockParameter); + } + + + public Task PositionPropsLengthQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task PricePrecisionQueryAsync(PricePrecisionFunction pricePrecisionFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(pricePrecisionFunction, blockParameter); + } + + + public Task PricePrecisionQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task UsdgDecimalsQueryAsync(UsdgDecimalsFunction usdgDecimalsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(usdgDecimalsFunction, blockParameter); + } + + + public Task UsdgDecimalsQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task GetAmountOutQueryAsync(GetAmountOutFunction getAmountOutFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getAmountOutFunction, blockParameter); + } + + public Task GetAmountOutQueryAsync(string vault, string tokenIn, string tokenOut, BigInteger amountIn, BlockParameter blockParameter = null) + { + var getAmountOutFunction = new GetAmountOutFunction(); + getAmountOutFunction.Vault = vault; + getAmountOutFunction.TokenIn = tokenIn; + getAmountOutFunction.TokenOut = tokenOut; + getAmountOutFunction.AmountIn = amountIn; + + return ContractHandler.QueryDeserializingToObjectAsync(getAmountOutFunction, blockParameter); + } + + public Task GetFeeBasisPointsQueryAsync(GetFeeBasisPointsFunction getFeeBasisPointsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryDeserializingToObjectAsync(getFeeBasisPointsFunction, blockParameter); + } + + public Task GetFeeBasisPointsQueryAsync(string vault, string tokenIn, string tokenOut, BigInteger amountIn, BlockParameter blockParameter = null) + { + var getFeeBasisPointsFunction = new GetFeeBasisPointsFunction(); + getFeeBasisPointsFunction.Vault = vault; + getFeeBasisPointsFunction.TokenIn = tokenIn; + getFeeBasisPointsFunction.TokenOut = tokenOut; + getFeeBasisPointsFunction.AmountIn = amountIn; + + return ContractHandler.QueryDeserializingToObjectAsync(getFeeBasisPointsFunction, blockParameter); + } + + public Task> GetFeesQueryAsync(GetFeesFunction getFeesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getFeesFunction, blockParameter); + } + + + public Task> GetFeesQueryAsync(string vault, List tokens, BlockParameter blockParameter = null) + { + var getFeesFunction = new GetFeesFunction(); + getFeesFunction.Vault = vault; + getFeesFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getFeesFunction, blockParameter); + } + + public Task> GetFullVaultTokenInfoQueryAsync(GetFullVaultTokenInfoFunction getFullVaultTokenInfoFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getFullVaultTokenInfoFunction, blockParameter); + } + + + public Task> GetFullVaultTokenInfoQueryAsync(string vault, string weth, BigInteger usdgAmount, List tokens, BlockParameter blockParameter = null) + { + var getFullVaultTokenInfoFunction = new GetFullVaultTokenInfoFunction(); + getFullVaultTokenInfoFunction.Vault = vault; + getFullVaultTokenInfoFunction.Weth = weth; + getFullVaultTokenInfoFunction.UsdgAmount = usdgAmount; + getFullVaultTokenInfoFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getFullVaultTokenInfoFunction, blockParameter); + } + + public Task> GetFundingRatesQueryAsync(GetFundingRatesFunction getFundingRatesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getFundingRatesFunction, blockParameter); + } + + + public Task> GetFundingRatesQueryAsync(string vault, string weth, List tokens, BlockParameter blockParameter = null) + { + var getFundingRatesFunction = new GetFundingRatesFunction(); + getFundingRatesFunction.Vault = vault; + getFundingRatesFunction.Weth = weth; + getFundingRatesFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getFundingRatesFunction, blockParameter); + } + + public Task GetMaxAmountInQueryAsync(GetMaxAmountInFunction getMaxAmountInFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(getMaxAmountInFunction, blockParameter); + } + + + public Task GetMaxAmountInQueryAsync(string vault, string tokenIn, string tokenOut, BlockParameter blockParameter = null) + { + var getMaxAmountInFunction = new GetMaxAmountInFunction(); + getMaxAmountInFunction.Vault = vault; + getMaxAmountInFunction.TokenIn = tokenIn; + getMaxAmountInFunction.TokenOut = tokenOut; + + return ContractHandler.QueryAsync(getMaxAmountInFunction, blockParameter); + } + + public Task> GetPairInfoQueryAsync(GetPairInfoFunction getPairInfoFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getPairInfoFunction, blockParameter); + } + + + public Task> GetPairInfoQueryAsync(string factory, List tokens, BlockParameter blockParameter = null) + { + var getPairInfoFunction = new GetPairInfoFunction(); + getPairInfoFunction.Factory = factory; + getPairInfoFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getPairInfoFunction, blockParameter); + } + + public Task> GetPositionsQueryAsync(GetPositionsFunction getPositionsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getPositionsFunction, blockParameter); + } + + + public Task> GetPositionsQueryAsync(string vault, string account, List collateralTokens, List indexTokens, List isLong, BlockParameter blockParameter = null) + { + var getPositionsFunction = new GetPositionsFunction(); + getPositionsFunction.Vault = vault; + getPositionsFunction.Account = account; + getPositionsFunction.CollateralTokens = collateralTokens; + getPositionsFunction.IndexTokens = indexTokens; + getPositionsFunction.IsLong = isLong; + + return ContractHandler.QueryAsync>(getPositionsFunction, blockParameter); + } + + public Task> GetPricesQueryAsync(GetPricesFunction getPricesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getPricesFunction, blockParameter); + } + + + public Task> GetPricesQueryAsync(string priceFeed, List tokens, BlockParameter blockParameter = null) + { + var getPricesFunction = new GetPricesFunction(); + getPricesFunction.PriceFeed = priceFeed; + getPricesFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getPricesFunction, blockParameter); + } + + public Task> GetStakingInfoQueryAsync(GetStakingInfoFunction getStakingInfoFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getStakingInfoFunction, blockParameter); + } + + + public Task> GetStakingInfoQueryAsync(string account, List yieldTrackers, BlockParameter blockParameter = null) + { + var getStakingInfoFunction = new GetStakingInfoFunction(); + getStakingInfoFunction.Account = account; + getStakingInfoFunction.YieldTrackers = yieldTrackers; + + return ContractHandler.QueryAsync>(getStakingInfoFunction, blockParameter); + } + + public Task> GetTokenBalancesQueryAsync(GetTokenBalancesFunction getTokenBalancesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getTokenBalancesFunction, blockParameter); + } + + + public Task> GetTokenBalancesQueryAsync(string account, List tokens, BlockParameter blockParameter = null) + { + var getTokenBalancesFunction = new GetTokenBalancesFunction(); + getTokenBalancesFunction.Account = account; + getTokenBalancesFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getTokenBalancesFunction, blockParameter); + } + + public Task> GetTokenBalancesWithSuppliesQueryAsync(GetTokenBalancesWithSuppliesFunction getTokenBalancesWithSuppliesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getTokenBalancesWithSuppliesFunction, blockParameter); + } + + + public Task> GetTokenBalancesWithSuppliesQueryAsync(string account, List tokens, BlockParameter blockParameter = null) + { + var getTokenBalancesWithSuppliesFunction = new GetTokenBalancesWithSuppliesFunction(); + getTokenBalancesWithSuppliesFunction.Account = account; + getTokenBalancesWithSuppliesFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getTokenBalancesWithSuppliesFunction, blockParameter); + } + + public Task GetTokenSupplyQueryAsync(GetTokenSupplyFunction getTokenSupplyFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(getTokenSupplyFunction, blockParameter); + } + + + public Task GetTokenSupplyQueryAsync(string token, List excludedAccounts, BlockParameter blockParameter = null) + { + var getTokenSupplyFunction = new GetTokenSupplyFunction(); + getTokenSupplyFunction.Token = token; + getTokenSupplyFunction.ExcludedAccounts = excludedAccounts; + + return ContractHandler.QueryAsync(getTokenSupplyFunction, blockParameter); + } + + public Task GetTotalBalanceQueryAsync(GetTotalBalanceFunction getTotalBalanceFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(getTotalBalanceFunction, blockParameter); + } + + + public Task GetTotalBalanceQueryAsync(string token, List accounts, BlockParameter blockParameter = null) + { + var getTotalBalanceFunction = new GetTotalBalanceFunction(); + getTotalBalanceFunction.Token = token; + getTotalBalanceFunction.Accounts = accounts; + + return ContractHandler.QueryAsync(getTotalBalanceFunction, blockParameter); + } + + public Task> GetTotalStakedQueryAsync(GetTotalStakedFunction getTotalStakedFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getTotalStakedFunction, blockParameter); + } + + + public Task> GetTotalStakedQueryAsync(List yieldTokens, BlockParameter blockParameter = null) + { + var getTotalStakedFunction = new GetTotalStakedFunction(); + getTotalStakedFunction.YieldTokens = yieldTokens; + + return ContractHandler.QueryAsync>(getTotalStakedFunction, blockParameter); + } + + public Task> GetVaultTokenInfoQueryAsync(GetVaultTokenInfoFunction getVaultTokenInfoFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getVaultTokenInfoFunction, blockParameter); + } + + + public Task> GetVaultTokenInfoQueryAsync(string vault, string weth, BigInteger usdgAmount, List tokens, BlockParameter blockParameter = null) + { + var getVaultTokenInfoFunction = new GetVaultTokenInfoFunction(); + getVaultTokenInfoFunction.Vault = vault; + getVaultTokenInfoFunction.Weth = weth; + getVaultTokenInfoFunction.UsdgAmount = usdgAmount; + getVaultTokenInfoFunction.Tokens = tokens; + + return ContractHandler.QueryAsync>(getVaultTokenInfoFunction, blockParameter); + } + + public Task> GetVaultTokenInfoV2QueryAsync(GetVaultTokenInfoV2Function getVaultTokenInfoV2Function, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getVaultTokenInfoV2Function, blockParameter); + } + + + public Task> GetVaultTokenInfoV2QueryAsync(string vault, string weth, BigInteger usdgAmount, List tokens, BlockParameter blockParameter = null) + { + var getVaultTokenInfoV2Function = new GetVaultTokenInfoV2Function(); + getVaultTokenInfoV2Function.Vault = vault; + getVaultTokenInfoV2Function.Weth = weth; + getVaultTokenInfoV2Function.UsdgAmount = usdgAmount; + getVaultTokenInfoV2Function.Tokens = tokens; + + return ContractHandler.QueryAsync>(getVaultTokenInfoV2Function, blockParameter); + } + + public Task> GetVestingInfoQueryAsync(GetVestingInfoFunction getVestingInfoFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync>(getVestingInfoFunction, blockParameter); + } + + + public Task> GetVestingInfoQueryAsync(string account, List vesters, BlockParameter blockParameter = null) + { + var getVestingInfoFunction = new GetVestingInfoFunction(); + getVestingInfoFunction.Account = account; + getVestingInfoFunction.Vesters = vesters; + + return ContractHandler.QueryAsync>(getVestingInfoFunction, blockParameter); + } + + public Task GovQueryAsync(GovFunction govFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(govFunction, blockParameter); + } + + + public Task GovQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task HasMaxGlobalShortSizesQueryAsync(HasMaxGlobalShortSizesFunction hasMaxGlobalShortSizesFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(hasMaxGlobalShortSizesFunction, blockParameter); + } + + + public Task HasMaxGlobalShortSizesQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task SetConfigRequestAsync(SetConfigFunction setConfigFunction) + { + return ContractHandler.SendRequestAsync(setConfigFunction); + } + + public Task SetConfigRequestAndWaitForReceiptAsync(SetConfigFunction setConfigFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setConfigFunction, cancellationToken); + } + + public Task SetConfigRequestAsync(bool hasMaxGlobalShortSizes) + { + var setConfigFunction = new SetConfigFunction(); + setConfigFunction.HasMaxGlobalShortSizes = hasMaxGlobalShortSizes; + + return ContractHandler.SendRequestAsync(setConfigFunction); + } + + public Task SetConfigRequestAndWaitForReceiptAsync(bool hasMaxGlobalShortSizes, CancellationTokenSource cancellationToken = null) + { + var setConfigFunction = new SetConfigFunction(); + setConfigFunction.HasMaxGlobalShortSizes = hasMaxGlobalShortSizes; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setConfigFunction, cancellationToken); + } + + public Task SetGovRequestAsync(SetGovFunction setGovFunction) + { + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(SetGovFunction setGovFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetGovRequestAsync(string gov) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(string gov, CancellationTokenSource cancellationToken = null) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + } +} diff --git a/src/Managing.Tools.ABI/Router/ContractDefinition/RouterDefinition.cs b/src/Managing.Tools.ABI/Router/ContractDefinition/RouterDefinition.cs new file mode 100644 index 0000000..eee8d03 --- /dev/null +++ b/src/Managing.Tools.ABI/Router/ContractDefinition/RouterDefinition.cs @@ -0,0 +1,466 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; + +namespace Managing.Tools.Router.ContractDefinition +{ + + + public partial class RouterDeployment : RouterDeploymentBase + { + public RouterDeployment() : base(BYTECODE) { } + public RouterDeployment(string byteCode) : base(byteCode) { } + } + + public class RouterDeploymentBase : ContractDeploymentMessage + { + public static string BYTECODE = "608060405234801561001057600080fd5b506040516124773803806124778339818101604052606081101561003357600080fd5b5080516020820151604090920151600380546001600160a01b039384166001600160a01b0319918216179091556002805494841694821694909417909355600180549290911691831691909117905560008054909116331790556123db8061009c6000396000f3fe60806040526004361061011f5760003560e01c806312d43a51146101835780631b827878146101b45780631f1dd176146101fd5780632662166b1461024e5780632d4ba6a7146102c15780633039e37f1461038357806338c74dd9146104635780633fc8cef314610496578063430ed37c146104ab5780634b12e6431461050a5780635fc8500e146105515780636023e9661461063157806390205d8c146106f357806390b64ad314610752578063956f285e1461078b578063a4d95b64146107c6578063abe68eaa146107f9578063b32755de146108a8578063b7ddc9921461096b578063cedd437514610a41578063cfad57a214610a74578063d8867fc814610aa7578063f5b91b7b14610ada578063fbfa77cf14610aef5761017e565b3661017e576001546001600160a01b0316331461017c576040805162461bcd60e51b81526020600482015260166024820152752937baba32b91d1034b73b30b634b21039b2b73232b960511b604482015290519081900360640190fd5b005b600080fd5b34801561018f57600080fd5b50610198610b04565b604080516001600160a01b039092168252519081900360200190f35b3480156101c057600080fd5b5061017c600480360360808110156101d757600080fd5b506001600160a01b03813581169160208101358216916040820135169060600135610b13565b34801561020957600080fd5b5061017c600480360360a081101561022057600080fd5b506001600160a01b038135811691602081013582169160408201351690606081013590608001351515610b37565b34801561025a57600080fd5b506102af600480360360e081101561027157600080fd5b506001600160a01b0381358116916020810135821691604082013581169160608101359160808201359160a081013515159160c09091013516610bcc565b60408051918252519081900360200190f35b3480156102cd57600080fd5b5061017c600480360360808110156102e457600080fd5b810190602081018135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460208302840111600160201b8311171561033157600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604001356001600160a01b0316610c8c565b34801561038f57600080fd5b5061017c60048036036101008110156103a757600080fd5b810190602081018135600160201b8111156103c157600080fd5b8201836020820111156103d357600080fd5b803590602001918460208302840111600160201b831117156103f457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b0390811693506020830135926040810135925060608101351515916080820135169060a08101359060c00135610df3565b34801561046f57600080fd5b5061017c6004803603602081101561048657600080fd5b50356001600160a01b0316610ef8565b3480156104a257600080fd5b50610198610f29565b3480156104b757600080fd5b5061017c600480360360e08110156104ce57600080fd5b506001600160a01b0381358116916020810135821691604082013591606081013591608082013515159160a08101359091169060c00135610f38565b34801561051657600080fd5b5061053d6004803603602081101561052d57600080fd5b50356001600160a01b0316610f5f565b604080519115158252519081900360200190f35b34801561055d57600080fd5b5061017c600480360361010081101561057557600080fd5b810190602081018135600160201b81111561058f57600080fd5b8201836020820111156105a157600080fd5b803590602001918460208302840111600160201b831117156105c257600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505081356001600160a01b0390811693506020830135926040810135925060608101351515916080820135169060a08101359060c00135610f74565b34801561063d57600080fd5b5061017c6004803603608081101561065457600080fd5b810190602081018135600160201b81111561066e57600080fd5b82018360208201111561068057600080fd5b803590602001918460208302840111600160201b831117156106a157600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602081013590604001356001600160a01b0316610fba565b3480156106ff57600080fd5b5061017c600480360360e081101561071657600080fd5b506001600160a01b0381358116916020810135821691604082013591606081013591608082013515159160a08101359091169060c00135610ff1565b34801561075e57600080fd5b5061017c6004803603604081101561077557600080fd5b506001600160a01b038135169060200135611000565b34801561079757600080fd5b5061053d600480360360408110156107ae57600080fd5b506001600160a01b038135811691602001351661108c565b3480156107d257600080fd5b5061017c600480360360208110156107e957600080fd5b50356001600160a01b03166110ac565b61017c6004803603606081101561080f57600080fd5b810190602081018135600160201b81111561082957600080fd5b82018360208201111561083b57600080fd5b803590602001918460208302840111600160201b8311171561085c57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955050823593505050602001356001600160a01b0316611120565b61017c600480360360c08110156108be57600080fd5b810190602081018135600160201b8111156108d857600080fd5b8201836020820111156108ea57600080fd5b803590602001918460208302840111600160201b8311171561090b57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383351693505050602081013590604081013590606081013515159060800135611235565b34801561097757600080fd5b5061017c600480360360e081101561098e57600080fd5b810190602081018135600160201b8111156109a857600080fd5b8201836020820111156109ba57600080fd5b803590602001918460208302840111600160201b831117156109db57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383351693505050602081013590604081013590606081013590608081013515159060a00135611325565b348015610a4d57600080fd5b5061017c60048036036020811015610a6457600080fd5b50356001600160a01b03166113d2565b348015610a8057600080fd5b5061017c60048036036020811015610a9757600080fd5b50356001600160a01b0316611400565b348015610ab357600080fd5b5061017c60048036036020811015610aca57600080fd5b50356001600160a01b0316611475565b348015610ae657600080fd5b506101986114ec565b348015610afb57600080fd5b506101986114fb565b6000546001600160a01b031681565b610b1c8361150a565b610b316001600160a01b0385168484846115e0565b50505050565b610b408561150a565b600354604080516348d91abf60e01b81526001600160a01b03888116600483015287811660248301528681166044830152606482018690528415156084830152915191909216916348d91abf9160a480830192600092919082900301818387803b158015610bad57600080fd5b505af1158015610bc1573d6000803e3d6000fd5b505050505050505050565b6000610bd78861150a565b6003546040805163082a084960e41b81526001600160a01b038b811660048301528a811660248301528981166044830152606482018990526084820188905286151560a483015285811660c4830152915191909216916382a084909160e48083019260209291908290030181600087803b158015610c5457600080fd5b505af1158015610c68573d6000803e3d6000fd5b505050506040513d6020811015610c7e57600080fd5b505198975050505050505050565b60015484516001600160a01b039091169085906000198101908110610cad57fe5b60200260200101516001600160a01b031614610cfe576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b610d48610d0961163a565b60035486516001600160a01b039091169086908890600090610d2757fe5b60200260200101516001600160a01b03166115e0909392919063ffffffff16565b6000610d5585843061163e565b9050610d61818361177e565b6000805160206123388339815191523386600081518110610d7e57fe5b602002602001015187600189510381518110610d9657fe5b6020026020010151878560405180866001600160a01b03168152602001856001600160a01b03168152602001846001600160a01b031681526020018381526020018281526020019550505050505060405180910390a15050505050565b60015488516001600160a01b039091169089906000198101908110610e1457fe5b60200260200101516001600160a01b031614610e65576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b6000610e8a89600081518110610e7757fe5b60200260200101518989898930896117fb565b9050610ed3600360009054906101000a90046001600160a01b0316828b600081518110610eb357fe5b60200260200101516001600160a01b0316611a089092919063ffffffff16565b6000610ee08a843061163e565b9050610eec818661177e565b50505050505050505050565b3360009081526005602090815260408083206001600160a01b0394909416835292905220805460ff19166001179055565b6001546001600160a01b031681565b6000610f49888888888830886117fb565b9050610f55818461177e565b5050505050505050565b60046020526000908152604090205460ff1681565b6000610f8689600081518110610e7757fe5b9050610faf600360009054906101000a90046001600160a01b0316828b600081518110610eb357fe5b610eec89838661163e565b610fc5610d0961163a565b6000610fd285848461163e565b90506000805160206123388339815191523386600081518110610d7e57fe5b610f55878787878787876117fb565b61102261100b61163a565b6003546001600160a01b03858116929116846115e0565b60035460408051635f7bc11960e01b81526001600160a01b03858116600483015291519190921691635f7bc11991602480830192600092919082900301818387803b15801561107057600080fd5b505af1158015611084573d6000803e3d6000fd5b505050505050565b600560209081526000928352604080842090915290825290205460ff1681565b6000546001600160a01b031633146110ff576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b6001600160a01b03166000908152600460205260409020805460ff19169055565b60015483516001600160a01b0390911690849060009061113c57fe5b60200260200101516001600160a01b03161461118d576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b611195611a5f565b60006111a284848461163e565b905060008051602061233883398151915233856000815181106111c157fe5b6020026020010151866001885103815181106111d957fe5b6020026020010151348560405180866001600160a01b03168152602001856001600160a01b03168152602001846001600160a01b031681526020018381526020018281526020019550505050505060405180910390a150505050565b60015486516001600160a01b0390911690879060009061125157fe5b60200260200101516001600160a01b0316146112a2576040805162461bcd60e51b81526020600482015260156024820152600080516020612295833981519152604482015290519081900360640190fd5b34156112b0576112b0611a5f565b600186511180156112c15750600034115b156113015760006112d387863061163e565b90506112ff600360009054906101000a90046001600160a01b0316828960018b510381518110610eb357fe5b505b6110848660018851038151811061131457fe5b602002602001015186858585611ae8565b84156113545761135461133661163a565b60035489516001600160a01b039091169088908b90600090610d2757fe5b600187511180156113655750600085115b156113a557600061137788863061163e565b90506113a3600360009054906101000a90046001600160a01b0316828a60018c510381518110610eb357fe5b505b6113c9876001895103815181106113b857fe5b602002602001015187858585611ae8565b50505050505050565b3360009081526005602090815260408083206001600160a01b0394909416835292905220805460ff19169055565b6000546001600160a01b03163314611453576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146114c8576040805162461bcd60e51b81526020600482015260116024820152702937baba32b91d103337b93134b23232b760791b604482015290519081900360640190fd5b6001600160a01b03166000908152600460205260409020805460ff19166001179055565b6002546001600160a01b031681565b6003546001600160a01b031681565b3360009081526004602052604090205460ff16611567576040805162461bcd60e51b81526020600482015260166024820152752937baba32b91d1034b73b30b634b21038363ab3b4b760511b604482015290519081900360640190fd5b6001600160a01b038116600090815260056020908152604080832033845290915290205460ff166115dd576040805162461bcd60e51b815260206004820152601b60248201527a149bdd5d195c8e881c1b1d59da5b881b9bdd08185c1c1c9bdd9959602a1b604482015290519081900360640190fd5b50565b604080516001600160a01b0380861660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610b31908590611ce3565b3390565b60008351600214156116855761167e8460008151811061165a57fe5b60200260200101518560018151811061166f57fe5b60200260200101518585611d94565b9050611777565b83516003141561172b5760006116c6856000815181106116a157fe5b6020026020010151866001815181106116b657fe5b6020026020010151600030611d94565b90506116ef600360009054906101000a90046001600160a01b03168287600181518110610eb357fe5b611723856001815181106116ff57fe5b60200260200101518660028151811061171457fe5b60200260200101518686611d94565b915050611777565b6040805162461bcd60e51b815260206004820152601c60248201527b0a4deeae8cae47440d2dcecc2d8d2c840bee0c2e8d05cd8cadccee8d60231b604482015290519081900360640190fd5b9392505050565b60015460408051632e1a7d4d60e01b81526004810185905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b1580156117cb57600080fd5b505af11580156117df573d6000803e3d6000fd5b506117f7925050506001600160a01b03821683611f92565b5050565b600083156118c157600354604080516340d3096b60e11b81526001600160a01b038a811660048301529151859392909216916381a612d691602480820192602092909190829003018186803b15801561185357600080fd5b505afa158015611867573d6000803e3d6000fd5b505050506040513d602081101561187d57600080fd5b505110156118bc5760405162461bcd60e51b81526004018080602001828103825260238152602001806123156023913960400191505060405180910390fd5b61197a565b60035460408051637092736960e11b81526001600160a01b038a8116600483015291518593929092169163e124e6d291602480820192602092909190829003018186803b15801561191157600080fd5b505afa158015611925573d6000803e3d6000fd5b505050506040513d602081101561193b57600080fd5b5051111561197a5760405162461bcd60e51b81526004018080602001828103825260248152602001806123826024913960400191505060405180910390fd5b6003546001600160a01b03166382a0849061199361163a565b604080516001600160e01b031960e085901b1681526001600160a01b039283166004820152828d166024820152828c166044820152606481018b9052608481018a905288151560a482015291871660c48301525160e48083019260209291908290030181600087803b158015610c5457600080fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052611a5a908490611ce3565b505050565b600160009054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015611aaf57600080fd5b505af1158015611ac3573d6000803e3d6000fd5b5050600354600154611ae694506001600160a01b03908116935016905034611a08565b565b8115611bac5760035460408051637092736960e11b81526001600160a01b03878116600483015291518493929092169163e124e6d291602480820192602092909190829003018186803b158015611b3e57600080fd5b505afa158015611b52573d6000803e3d6000fd5b505050506040513d6020811015611b6857600080fd5b50511115611ba75760405162461bcd60e51b81526004018080602001828103825260248152602001806123826024913960400191505060405180910390fd5b611c65565b600354604080516340d3096b60e11b81526001600160a01b0387811660048301529151849392909216916381a612d691602480820192602092909190829003018186803b158015611bfc57600080fd5b505afa158015611c10573d6000803e3d6000fd5b505050506040513d6020811015611c2657600080fd5b50511015611c655760405162461bcd60e51b81526004018080602001828103825260238152602001806123156023913960400191505060405180910390fd5b6003546001600160a01b03166348d91abf611c7e61163a565b604080516001600160e01b031960e085901b1681526001600160a01b039283166004820152828a16602482015291881660448301526064820187905285151560848301525160a480830192600092919082900301818387803b158015610bad57600080fd5b6060611d38826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166120779092919063ffffffff16565b805190915015611a5a57808060200190516020811015611d5757600080fd5b5051611a5a5760405162461bcd60e51b815260040180806020018281038252602a815260200180612358602a913960400191505060405180910390fd5b60025460009081906001600160a01b0386811691161415611e39576003546040805163817bb85760e01b81526001600160a01b03898116600483015286811660248301529151919092169163817bb8579160448083019260209291908290030181600087803b158015611e0657600080fd5b505af1158015611e1a573d6000803e3d6000fd5b505050506040513d6020811015611e3057600080fd5b50519050611f34565b6002546001600160a01b0387811691161415611ea65760035460408051630711e61960e41b81526001600160a01b03888116600483015286811660248301529151919092169163711e61909160448083019260209291908290030181600087803b158015611e0657600080fd5b60035460408051634998b10960e11b81526001600160a01b038981166004830152888116602483015286811660448301529151919092169163933162129160648083019260209291908290030181600087803b158015611f0557600080fd5b505af1158015611f19573d6000803e3d6000fd5b505050506040513d6020811015611f2f57600080fd5b505190505b83811015611f89576040805162461bcd60e51b815260206004820152601e60248201527f526f757465723a20696e73756666696369656e7420616d6f756e744f75740000604482015290519081900360640190fd5b95945050505050565b80471015611fe7576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d8060008114612032576040519150601f19603f3d011682016040523d82523d6000602084013e612037565b606091505b5050905080611a5a5760405162461bcd60e51b815260040180806020018281038252603a8152602001806122b5603a913960400191505060405180910390fd5b6060612086848460008561208e565b949350505050565b6060824710156120cf5760405162461bcd60e51b81526004018080602001828103825260268152602001806122ef6026913960400191505060405180910390fd5b6120d8856121ea565b612129576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b602083106121685780518252601f199092019160209182019101612149565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d80600081146121ca576040519150601f19603f3d011682016040523d82523d6000602084013e6121cf565b606091505b50915091506121df8282866121f0565b979650505050505050565b3b151590565b606083156121ff575081611777565b82511561220f5782518084602001fd5b8160405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612259578181015183820152602001612241565b50505050905090810190601f1680156122865780820380516001836020036101000a031916815260200191505b509250505060405180910390fdfe526f757465723a20696e76616c6964205f706174680000000000000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c526f757465723a206d61726b207072696365206c6f776572207468616e206c696d6974cd3829a3813dc3cdd188fd3d01dcf3268c16be2fdd2dd21d0665418816e460625361666545524332303a204552433230206f7065726174696f6e20646964206e6f742073756363656564526f757465723a206d61726b20707269636520686967686572207468616e206c696d6974a26469706673582212205fb07c28854d2a79764cfd95b47807b48d7af91fddcab27d57d2e9b67560698464736f6c634300060c0033000000000000000000000000489ee077994b6658eafa855c308275ead8097c4a00000000000000000000000045096e7aa921f27590f8f19e457794eb0967814100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1"; + public RouterDeploymentBase() : base(BYTECODE) { } + public RouterDeploymentBase(string byteCode) : base(byteCode) { } + [Parameter("address", "_vault", 1)] + public virtual string Vault { get; set; } + [Parameter("address", "_usdg", 2)] + public virtual string Usdg { get; set; } + [Parameter("address", "_weth", 3)] + public virtual string Weth { get; set; } + } + + public partial class AddPluginFunction : AddPluginFunctionBase { } + + [Function("addPlugin")] + public class AddPluginFunctionBase : FunctionMessage + { + [Parameter("address", "_plugin", 1)] + public virtual string Plugin { get; set; } + } + + public partial class ApprovePluginFunction : ApprovePluginFunctionBase { } + + [Function("approvePlugin")] + public class ApprovePluginFunctionBase : FunctionMessage + { + [Parameter("address", "_plugin", 1)] + public virtual string Plugin { get; set; } + } + + public partial class ApprovedPluginsFunction : ApprovedPluginsFunctionBase { } + + [Function("approvedPlugins", "bool")] + public class ApprovedPluginsFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + [Parameter("address", "", 2)] + public virtual string ReturnValue2 { get; set; } + } + + public partial class DecreasePositionFunction : DecreasePositionFunctionBase { } + + [Function("decreasePosition")] + public class DecreasePositionFunctionBase : FunctionMessage + { + [Parameter("address", "_collateralToken", 1)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_price", 7)] + public virtual BigInteger Price { get; set; } + } + + public partial class DecreasePositionAndSwapFunction : DecreasePositionAndSwapFunctionBase { } + + [Function("decreasePositionAndSwap")] + public class DecreasePositionAndSwapFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_price", 7)] + public virtual BigInteger Price { get; set; } + [Parameter("uint256", "_minOut", 8)] + public virtual BigInteger MinOut { get; set; } + } + + public partial class DecreasePositionAndSwapETHFunction : DecreasePositionAndSwapETHFunctionBase { } + + [Function("decreasePositionAndSwapETH")] + public class DecreasePositionAndSwapETHFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_price", 7)] + public virtual BigInteger Price { get; set; } + [Parameter("uint256", "_minOut", 8)] + public virtual BigInteger MinOut { get; set; } + } + + public partial class DecreasePositionETHFunction : DecreasePositionETHFunctionBase { } + + [Function("decreasePositionETH")] + public class DecreasePositionETHFunctionBase : FunctionMessage + { + [Parameter("address", "_collateralToken", 1)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 3)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 6)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_price", 7)] + public virtual BigInteger Price { get; set; } + } + + public partial class DenyPluginFunction : DenyPluginFunctionBase { } + + [Function("denyPlugin")] + public class DenyPluginFunctionBase : FunctionMessage + { + [Parameter("address", "_plugin", 1)] + public virtual string Plugin { get; set; } + } + + public partial class DirectPoolDepositFunction : DirectPoolDepositFunctionBase { } + + [Function("directPoolDeposit")] + public class DirectPoolDepositFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("uint256", "_amount", 2)] + public virtual BigInteger Amount { get; set; } + } + + public partial class GovFunction : GovFunctionBase { } + + [Function("gov", "address")] + public class GovFunctionBase : FunctionMessage + { + + } + + public partial class IncreasePositionFunction : IncreasePositionFunctionBase { } + + [Function("increasePosition")] + public class IncreasePositionFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_amountIn", 3)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "_minOut", 4)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_price", 7)] + public virtual BigInteger Price { get; set; } + } + + public partial class IncreasePositionETHFunction : IncreasePositionETHFunctionBase { } + + [Function("increasePositionETH")] + public class IncreasePositionETHFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("address", "_indexToken", 2)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + [Parameter("uint256", "_price", 6)] + public virtual BigInteger Price { get; set; } + } + + public partial class PluginDecreasePositionFunction : PluginDecreasePositionFunctionBase { } + + [Function("pluginDecreasePosition", "uint256")] + public class PluginDecreasePositionFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "_collateralToken", 2)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "_indexToken", 3)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_collateralDelta", 4)] + public virtual BigInteger CollateralDelta { get; set; } + [Parameter("uint256", "_sizeDelta", 5)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 6)] + public virtual bool IsLong { get; set; } + [Parameter("address", "_receiver", 7)] + public virtual string Receiver { get; set; } + } + + public partial class PluginIncreasePositionFunction : PluginIncreasePositionFunctionBase { } + + [Function("pluginIncreasePosition")] + public class PluginIncreasePositionFunctionBase : FunctionMessage + { + [Parameter("address", "_account", 1)] + public virtual string Account { get; set; } + [Parameter("address", "_collateralToken", 2)] + public virtual string CollateralToken { get; set; } + [Parameter("address", "_indexToken", 3)] + public virtual string IndexToken { get; set; } + [Parameter("uint256", "_sizeDelta", 4)] + public virtual BigInteger SizeDelta { get; set; } + [Parameter("bool", "_isLong", 5)] + public virtual bool IsLong { get; set; } + } + + public partial class PluginTransferFunction : PluginTransferFunctionBase { } + + [Function("pluginTransfer")] + public class PluginTransferFunctionBase : FunctionMessage + { + [Parameter("address", "_token", 1)] + public virtual string Token { get; set; } + [Parameter("address", "_account", 2)] + public virtual string Account { get; set; } + [Parameter("address", "_receiver", 3)] + public virtual string Receiver { get; set; } + [Parameter("uint256", "_amount", 4)] + public virtual BigInteger Amount { get; set; } + } + + public partial class PluginsFunction : PluginsFunctionBase { } + + [Function("plugins", "bool")] + public class PluginsFunctionBase : FunctionMessage + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class RemovePluginFunction : RemovePluginFunctionBase { } + + [Function("removePlugin")] + public class RemovePluginFunctionBase : FunctionMessage + { + [Parameter("address", "_plugin", 1)] + public virtual string Plugin { get; set; } + } + + public partial class SetGovFunction : SetGovFunctionBase { } + + [Function("setGov")] + public class SetGovFunctionBase : FunctionMessage + { + [Parameter("address", "_gov", 1)] + public virtual string Gov { get; set; } + } + + public partial class SwapFunction : SwapFunctionBase { } + + [Function("swap")] + public class SwapFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_amountIn", 2)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "_minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("address", "_receiver", 4)] + public virtual string Receiver { get; set; } + } + + public partial class SwapETHToTokensFunction : SwapETHToTokensFunctionBase { } + + [Function("swapETHToTokens")] + public class SwapETHToTokensFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_minOut", 2)] + public virtual BigInteger MinOut { get; set; } + [Parameter("address", "_receiver", 3)] + public virtual string Receiver { get; set; } + } + + public partial class SwapTokensToETHFunction : SwapTokensToETHFunctionBase { } + + [Function("swapTokensToETH")] + public class SwapTokensToETHFunctionBase : FunctionMessage + { + [Parameter("address[]", "_path", 1)] + public virtual List Path { get; set; } + [Parameter("uint256", "_amountIn", 2)] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "_minOut", 3)] + public virtual BigInteger MinOut { get; set; } + [Parameter("address", "_receiver", 4)] + public virtual string Receiver { get; set; } + } + + public partial class UsdgFunction : UsdgFunctionBase { } + + [Function("usdg", "address")] + public class UsdgFunctionBase : FunctionMessage + { + + } + + public partial class VaultFunction : VaultFunctionBase { } + + [Function("vault", "address")] + public class VaultFunctionBase : FunctionMessage + { + + } + + public partial class WethFunction : WethFunctionBase { } + + [Function("weth", "address")] + public class WethFunctionBase : FunctionMessage + { + + } + + public partial class SwapEventDTO : SwapEventDTOBase { } + + [Event("Swap")] + public class SwapEventDTOBase : IEventDTO + { + [Parameter("address", "account", 1, false )] + public virtual string Account { get; set; } + [Parameter("address", "tokenIn", 2, false )] + public virtual string TokenIn { get; set; } + [Parameter("address", "tokenOut", 3, false )] + public virtual string TokenOut { get; set; } + [Parameter("uint256", "amountIn", 4, false )] + public virtual BigInteger AmountIn { get; set; } + [Parameter("uint256", "amountOut", 5, false )] + public virtual BigInteger AmountOut { get; set; } + } + + + + + + public partial class ApprovedPluginsOutputDTO : ApprovedPluginsOutputDTOBase { } + + [FunctionOutput] + public class ApprovedPluginsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + + + + + + + + + + + + + public partial class GovOutputDTO : GovOutputDTOBase { } + + [FunctionOutput] + public class GovOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + + + + + + + + + + + public partial class PluginsOutputDTO : PluginsOutputDTOBase { } + + [FunctionOutput] + public class PluginsOutputDTOBase : IFunctionOutputDTO + { + [Parameter("bool", "", 1)] + public virtual bool ReturnValue1 { get; set; } + } + + + + + + + + + + + + public partial class UsdgOutputDTO : UsdgOutputDTOBase { } + + [FunctionOutput] + public class UsdgOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class VaultOutputDTO : VaultOutputDTOBase { } + + [FunctionOutput] + public class VaultOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } + + public partial class WethOutputDTO : WethOutputDTOBase { } + + [FunctionOutput] + public class WethOutputDTOBase : IFunctionOutputDTO + { + [Parameter("address", "", 1)] + public virtual string ReturnValue1 { get; set; } + } +} diff --git a/src/Managing.Tools.ABI/Router/RouterService.cs b/src/Managing.Tools.ABI/Router/RouterService.cs new file mode 100644 index 0000000..934f189 --- /dev/null +++ b/src/Managing.Tools.ABI/Router/RouterService.cs @@ -0,0 +1,702 @@ +using System.Numerics; +using Nethereum.Web3; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Contracts.ContractHandlers; +using Managing.Tools.Router.ContractDefinition; + +namespace Managing.Tools.Router +{ + public partial class RouterService + { + public static Task DeployContractAndWaitForReceiptAsync(Web3 web3, RouterDeployment routerDeployment, CancellationTokenSource cancellationTokenSource = null) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAndWaitForReceiptAsync(routerDeployment, cancellationTokenSource); + } + + public static Task DeployContractAsync(Web3 web3, RouterDeployment routerDeployment) + { + return web3.Eth.GetContractDeploymentHandler().SendRequestAsync(routerDeployment); + } + + public static async Task DeployContractAndGetServiceAsync(Web3 web3, RouterDeployment routerDeployment, CancellationTokenSource cancellationTokenSource = null) + { + var receipt = await DeployContractAndWaitForReceiptAsync(web3, routerDeployment, cancellationTokenSource); + return new RouterService(web3, receipt.ContractAddress); + } + + protected IWeb3 Web3 { get; } + + public ContractHandler ContractHandler { get; } + + public RouterService(Web3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public RouterService(IWeb3 web3, string contractAddress) + { + Web3 = web3; + ContractHandler = web3.Eth.GetContractHandler(contractAddress); + } + + public Task AddPluginRequestAsync(AddPluginFunction addPluginFunction) + { + return ContractHandler.SendRequestAsync(addPluginFunction); + } + + public Task AddPluginRequestAndWaitForReceiptAsync(AddPluginFunction addPluginFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(addPluginFunction, cancellationToken); + } + + public Task AddPluginRequestAsync(string plugin) + { + var addPluginFunction = new AddPluginFunction(); + addPluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAsync(addPluginFunction); + } + + public Task AddPluginRequestAndWaitForReceiptAsync(string plugin, CancellationTokenSource cancellationToken = null) + { + var addPluginFunction = new AddPluginFunction(); + addPluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(addPluginFunction, cancellationToken); + } + + public Task ApprovePluginRequestAsync(ApprovePluginFunction approvePluginFunction) + { + return ContractHandler.SendRequestAsync(approvePluginFunction); + } + + public Task ApprovePluginRequestAndWaitForReceiptAsync(ApprovePluginFunction approvePluginFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(approvePluginFunction, cancellationToken); + } + + public Task ApprovePluginRequestAsync(string plugin) + { + var approvePluginFunction = new ApprovePluginFunction(); + approvePluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAsync(approvePluginFunction); + } + + public Task ApprovePluginRequestAndWaitForReceiptAsync(string plugin, CancellationTokenSource cancellationToken = null) + { + var approvePluginFunction = new ApprovePluginFunction(); + approvePluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(approvePluginFunction, cancellationToken); + } + + public Task ApprovedPluginsQueryAsync(ApprovedPluginsFunction approvedPluginsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(approvedPluginsFunction, blockParameter); + } + + + public Task ApprovedPluginsQueryAsync(string returnValue1, string returnValue2, BlockParameter blockParameter = null) + { + var approvedPluginsFunction = new ApprovedPluginsFunction(); + approvedPluginsFunction.ReturnValue1 = returnValue1; + approvedPluginsFunction.ReturnValue2 = returnValue2; + + return ContractHandler.QueryAsync(approvedPluginsFunction, blockParameter); + } + + public Task DecreasePositionRequestAsync(DecreasePositionFunction decreasePositionFunction) + { + return ContractHandler.SendRequestAsync(decreasePositionFunction); + } + + public Task DecreasePositionRequestAndWaitForReceiptAsync(DecreasePositionFunction decreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionFunction, cancellationToken); + } + + public Task DecreasePositionRequestAsync(string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price) + { + var decreasePositionFunction = new DecreasePositionFunction(); + decreasePositionFunction.CollateralToken = collateralToken; + decreasePositionFunction.IndexToken = indexToken; + decreasePositionFunction.CollateralDelta = collateralDelta; + decreasePositionFunction.SizeDelta = sizeDelta; + decreasePositionFunction.IsLong = isLong; + decreasePositionFunction.Receiver = receiver; + decreasePositionFunction.Price = price; + + return ContractHandler.SendRequestAsync(decreasePositionFunction); + } + + public Task DecreasePositionRequestAndWaitForReceiptAsync(string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, CancellationTokenSource cancellationToken = null) + { + var decreasePositionFunction = new DecreasePositionFunction(); + decreasePositionFunction.CollateralToken = collateralToken; + decreasePositionFunction.IndexToken = indexToken; + decreasePositionFunction.CollateralDelta = collateralDelta; + decreasePositionFunction.SizeDelta = sizeDelta; + decreasePositionFunction.IsLong = isLong; + decreasePositionFunction.Receiver = receiver; + decreasePositionFunction.Price = price; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionFunction, cancellationToken); + } + + public Task DecreasePositionAndSwapRequestAsync(DecreasePositionAndSwapFunction decreasePositionAndSwapFunction) + { + return ContractHandler.SendRequestAsync(decreasePositionAndSwapFunction); + } + + public Task DecreasePositionAndSwapRequestAndWaitForReceiptAsync(DecreasePositionAndSwapFunction decreasePositionAndSwapFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionAndSwapFunction, cancellationToken); + } + + public Task DecreasePositionAndSwapRequestAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, BigInteger minOut) + { + var decreasePositionAndSwapFunction = new DecreasePositionAndSwapFunction(); + decreasePositionAndSwapFunction.Path = path; + decreasePositionAndSwapFunction.IndexToken = indexToken; + decreasePositionAndSwapFunction.CollateralDelta = collateralDelta; + decreasePositionAndSwapFunction.SizeDelta = sizeDelta; + decreasePositionAndSwapFunction.IsLong = isLong; + decreasePositionAndSwapFunction.Receiver = receiver; + decreasePositionAndSwapFunction.Price = price; + decreasePositionAndSwapFunction.MinOut = minOut; + + return ContractHandler.SendRequestAsync(decreasePositionAndSwapFunction); + } + + public Task DecreasePositionAndSwapRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, BigInteger minOut, CancellationTokenSource cancellationToken = null) + { + var decreasePositionAndSwapFunction = new DecreasePositionAndSwapFunction(); + decreasePositionAndSwapFunction.Path = path; + decreasePositionAndSwapFunction.IndexToken = indexToken; + decreasePositionAndSwapFunction.CollateralDelta = collateralDelta; + decreasePositionAndSwapFunction.SizeDelta = sizeDelta; + decreasePositionAndSwapFunction.IsLong = isLong; + decreasePositionAndSwapFunction.Receiver = receiver; + decreasePositionAndSwapFunction.Price = price; + decreasePositionAndSwapFunction.MinOut = minOut; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionAndSwapFunction, cancellationToken); + } + + public Task DecreasePositionAndSwapETHRequestAsync(DecreasePositionAndSwapETHFunction decreasePositionAndSwapETHFunction) + { + return ContractHandler.SendRequestAsync(decreasePositionAndSwapETHFunction); + } + + public Task DecreasePositionAndSwapETHRequestAndWaitForReceiptAsync(DecreasePositionAndSwapETHFunction decreasePositionAndSwapETHFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionAndSwapETHFunction, cancellationToken); + } + + public Task DecreasePositionAndSwapETHRequestAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, BigInteger minOut) + { + var decreasePositionAndSwapETHFunction = new DecreasePositionAndSwapETHFunction(); + decreasePositionAndSwapETHFunction.Path = path; + decreasePositionAndSwapETHFunction.IndexToken = indexToken; + decreasePositionAndSwapETHFunction.CollateralDelta = collateralDelta; + decreasePositionAndSwapETHFunction.SizeDelta = sizeDelta; + decreasePositionAndSwapETHFunction.IsLong = isLong; + decreasePositionAndSwapETHFunction.Receiver = receiver; + decreasePositionAndSwapETHFunction.Price = price; + decreasePositionAndSwapETHFunction.MinOut = minOut; + + return ContractHandler.SendRequestAsync(decreasePositionAndSwapETHFunction); + } + + public Task DecreasePositionAndSwapETHRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, BigInteger minOut, CancellationTokenSource cancellationToken = null) + { + var decreasePositionAndSwapETHFunction = new DecreasePositionAndSwapETHFunction(); + decreasePositionAndSwapETHFunction.Path = path; + decreasePositionAndSwapETHFunction.IndexToken = indexToken; + decreasePositionAndSwapETHFunction.CollateralDelta = collateralDelta; + decreasePositionAndSwapETHFunction.SizeDelta = sizeDelta; + decreasePositionAndSwapETHFunction.IsLong = isLong; + decreasePositionAndSwapETHFunction.Receiver = receiver; + decreasePositionAndSwapETHFunction.Price = price; + decreasePositionAndSwapETHFunction.MinOut = minOut; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionAndSwapETHFunction, cancellationToken); + } + + public Task DecreasePositionETHRequestAsync(DecreasePositionETHFunction decreasePositionETHFunction) + { + return ContractHandler.SendRequestAsync(decreasePositionETHFunction); + } + + public Task DecreasePositionETHRequestAndWaitForReceiptAsync(DecreasePositionETHFunction decreasePositionETHFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionETHFunction, cancellationToken); + } + + public Task DecreasePositionETHRequestAsync(string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price) + { + var decreasePositionETHFunction = new DecreasePositionETHFunction(); + decreasePositionETHFunction.CollateralToken = collateralToken; + decreasePositionETHFunction.IndexToken = indexToken; + decreasePositionETHFunction.CollateralDelta = collateralDelta; + decreasePositionETHFunction.SizeDelta = sizeDelta; + decreasePositionETHFunction.IsLong = isLong; + decreasePositionETHFunction.Receiver = receiver; + decreasePositionETHFunction.Price = price; + + return ContractHandler.SendRequestAsync(decreasePositionETHFunction); + } + + public Task DecreasePositionETHRequestAndWaitForReceiptAsync(string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, BigInteger price, CancellationTokenSource cancellationToken = null) + { + var decreasePositionETHFunction = new DecreasePositionETHFunction(); + decreasePositionETHFunction.CollateralToken = collateralToken; + decreasePositionETHFunction.IndexToken = indexToken; + decreasePositionETHFunction.CollateralDelta = collateralDelta; + decreasePositionETHFunction.SizeDelta = sizeDelta; + decreasePositionETHFunction.IsLong = isLong; + decreasePositionETHFunction.Receiver = receiver; + decreasePositionETHFunction.Price = price; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(decreasePositionETHFunction, cancellationToken); + } + + public Task DenyPluginRequestAsync(DenyPluginFunction denyPluginFunction) + { + return ContractHandler.SendRequestAsync(denyPluginFunction); + } + + public Task DenyPluginRequestAndWaitForReceiptAsync(DenyPluginFunction denyPluginFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(denyPluginFunction, cancellationToken); + } + + public Task DenyPluginRequestAsync(string plugin) + { + var denyPluginFunction = new DenyPluginFunction(); + denyPluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAsync(denyPluginFunction); + } + + public Task DenyPluginRequestAndWaitForReceiptAsync(string plugin, CancellationTokenSource cancellationToken = null) + { + var denyPluginFunction = new DenyPluginFunction(); + denyPluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(denyPluginFunction, cancellationToken); + } + + public Task DirectPoolDepositRequestAsync(DirectPoolDepositFunction directPoolDepositFunction) + { + return ContractHandler.SendRequestAsync(directPoolDepositFunction); + } + + public Task DirectPoolDepositRequestAndWaitForReceiptAsync(DirectPoolDepositFunction directPoolDepositFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(directPoolDepositFunction, cancellationToken); + } + + public Task DirectPoolDepositRequestAsync(string token, BigInteger amount) + { + var directPoolDepositFunction = new DirectPoolDepositFunction(); + directPoolDepositFunction.Token = token; + directPoolDepositFunction.Amount = amount; + + return ContractHandler.SendRequestAsync(directPoolDepositFunction); + } + + public Task DirectPoolDepositRequestAndWaitForReceiptAsync(string token, BigInteger amount, CancellationTokenSource cancellationToken = null) + { + var directPoolDepositFunction = new DirectPoolDepositFunction(); + directPoolDepositFunction.Token = token; + directPoolDepositFunction.Amount = amount; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(directPoolDepositFunction, cancellationToken); + } + + public Task GovQueryAsync(GovFunction govFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(govFunction, blockParameter); + } + + + public Task GovQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task IncreasePositionRequestAsync(IncreasePositionFunction increasePositionFunction) + { + return ContractHandler.SendRequestAsync(increasePositionFunction); + } + + public Task IncreasePositionRequestAndWaitForReceiptAsync(IncreasePositionFunction increasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(increasePositionFunction, cancellationToken); + } + + public Task IncreasePositionRequestAsync(List path, string indexToken, BigInteger amountIn, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger price) + { + var increasePositionFunction = new IncreasePositionFunction(); + increasePositionFunction.Path = path; + increasePositionFunction.IndexToken = indexToken; + increasePositionFunction.AmountIn = amountIn; + increasePositionFunction.MinOut = minOut; + increasePositionFunction.SizeDelta = sizeDelta; + increasePositionFunction.IsLong = isLong; + increasePositionFunction.Price = price; + + return ContractHandler.SendRequestAsync(increasePositionFunction); + } + + public Task IncreasePositionRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger amountIn, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger price, CancellationTokenSource cancellationToken = null) + { + var increasePositionFunction = new IncreasePositionFunction(); + increasePositionFunction.Path = path; + increasePositionFunction.IndexToken = indexToken; + increasePositionFunction.AmountIn = amountIn; + increasePositionFunction.MinOut = minOut; + increasePositionFunction.SizeDelta = sizeDelta; + increasePositionFunction.IsLong = isLong; + increasePositionFunction.Price = price; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(increasePositionFunction, cancellationToken); + } + + public Task IncreasePositionETHRequestAsync(IncreasePositionETHFunction increasePositionETHFunction) + { + return ContractHandler.SendRequestAsync(increasePositionETHFunction); + } + + public Task IncreasePositionETHRequestAndWaitForReceiptAsync(IncreasePositionETHFunction increasePositionETHFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(increasePositionETHFunction, cancellationToken); + } + + public Task IncreasePositionETHRequestAsync(List path, string indexToken, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger price) + { + var increasePositionETHFunction = new IncreasePositionETHFunction(); + increasePositionETHFunction.Path = path; + increasePositionETHFunction.IndexToken = indexToken; + increasePositionETHFunction.MinOut = minOut; + increasePositionETHFunction.SizeDelta = sizeDelta; + increasePositionETHFunction.IsLong = isLong; + increasePositionETHFunction.Price = price; + + return ContractHandler.SendRequestAsync(increasePositionETHFunction); + } + + public Task IncreasePositionETHRequestAndWaitForReceiptAsync(List path, string indexToken, BigInteger minOut, BigInteger sizeDelta, bool isLong, BigInteger price, CancellationTokenSource cancellationToken = null) + { + var increasePositionETHFunction = new IncreasePositionETHFunction(); + increasePositionETHFunction.Path = path; + increasePositionETHFunction.IndexToken = indexToken; + increasePositionETHFunction.MinOut = minOut; + increasePositionETHFunction.SizeDelta = sizeDelta; + increasePositionETHFunction.IsLong = isLong; + increasePositionETHFunction.Price = price; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(increasePositionETHFunction, cancellationToken); + } + + public Task PluginDecreasePositionRequestAsync(PluginDecreasePositionFunction pluginDecreasePositionFunction) + { + return ContractHandler.SendRequestAsync(pluginDecreasePositionFunction); + } + + public Task PluginDecreasePositionRequestAndWaitForReceiptAsync(PluginDecreasePositionFunction pluginDecreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginDecreasePositionFunction, cancellationToken); + } + + public Task PluginDecreasePositionRequestAsync(string account, string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver) + { + var pluginDecreasePositionFunction = new PluginDecreasePositionFunction(); + pluginDecreasePositionFunction.Account = account; + pluginDecreasePositionFunction.CollateralToken = collateralToken; + pluginDecreasePositionFunction.IndexToken = indexToken; + pluginDecreasePositionFunction.CollateralDelta = collateralDelta; + pluginDecreasePositionFunction.SizeDelta = sizeDelta; + pluginDecreasePositionFunction.IsLong = isLong; + pluginDecreasePositionFunction.Receiver = receiver; + + return ContractHandler.SendRequestAsync(pluginDecreasePositionFunction); + } + + public Task PluginDecreasePositionRequestAndWaitForReceiptAsync(string account, string collateralToken, string indexToken, BigInteger collateralDelta, BigInteger sizeDelta, bool isLong, string receiver, CancellationTokenSource cancellationToken = null) + { + var pluginDecreasePositionFunction = new PluginDecreasePositionFunction(); + pluginDecreasePositionFunction.Account = account; + pluginDecreasePositionFunction.CollateralToken = collateralToken; + pluginDecreasePositionFunction.IndexToken = indexToken; + pluginDecreasePositionFunction.CollateralDelta = collateralDelta; + pluginDecreasePositionFunction.SizeDelta = sizeDelta; + pluginDecreasePositionFunction.IsLong = isLong; + pluginDecreasePositionFunction.Receiver = receiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginDecreasePositionFunction, cancellationToken); + } + + public Task PluginIncreasePositionRequestAsync(PluginIncreasePositionFunction pluginIncreasePositionFunction) + { + return ContractHandler.SendRequestAsync(pluginIncreasePositionFunction); + } + + public Task PluginIncreasePositionRequestAndWaitForReceiptAsync(PluginIncreasePositionFunction pluginIncreasePositionFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginIncreasePositionFunction, cancellationToken); + } + + public Task PluginIncreasePositionRequestAsync(string account, string collateralToken, string indexToken, BigInteger sizeDelta, bool isLong) + { + var pluginIncreasePositionFunction = new PluginIncreasePositionFunction(); + pluginIncreasePositionFunction.Account = account; + pluginIncreasePositionFunction.CollateralToken = collateralToken; + pluginIncreasePositionFunction.IndexToken = indexToken; + pluginIncreasePositionFunction.SizeDelta = sizeDelta; + pluginIncreasePositionFunction.IsLong = isLong; + + return ContractHandler.SendRequestAsync(pluginIncreasePositionFunction); + } + + public Task PluginIncreasePositionRequestAndWaitForReceiptAsync(string account, string collateralToken, string indexToken, BigInteger sizeDelta, bool isLong, CancellationTokenSource cancellationToken = null) + { + var pluginIncreasePositionFunction = new PluginIncreasePositionFunction(); + pluginIncreasePositionFunction.Account = account; + pluginIncreasePositionFunction.CollateralToken = collateralToken; + pluginIncreasePositionFunction.IndexToken = indexToken; + pluginIncreasePositionFunction.SizeDelta = sizeDelta; + pluginIncreasePositionFunction.IsLong = isLong; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginIncreasePositionFunction, cancellationToken); + } + + public Task PluginTransferRequestAsync(PluginTransferFunction pluginTransferFunction) + { + return ContractHandler.SendRequestAsync(pluginTransferFunction); + } + + public Task PluginTransferRequestAndWaitForReceiptAsync(PluginTransferFunction pluginTransferFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginTransferFunction, cancellationToken); + } + + public Task PluginTransferRequestAsync(string token, string account, string receiver, BigInteger amount) + { + var pluginTransferFunction = new PluginTransferFunction(); + pluginTransferFunction.Token = token; + pluginTransferFunction.Account = account; + pluginTransferFunction.Receiver = receiver; + pluginTransferFunction.Amount = amount; + + return ContractHandler.SendRequestAsync(pluginTransferFunction); + } + + public Task PluginTransferRequestAndWaitForReceiptAsync(string token, string account, string receiver, BigInteger amount, CancellationTokenSource cancellationToken = null) + { + var pluginTransferFunction = new PluginTransferFunction(); + pluginTransferFunction.Token = token; + pluginTransferFunction.Account = account; + pluginTransferFunction.Receiver = receiver; + pluginTransferFunction.Amount = amount; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(pluginTransferFunction, cancellationToken); + } + + public Task PluginsQueryAsync(PluginsFunction pluginsFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(pluginsFunction, blockParameter); + } + + + public Task PluginsQueryAsync(string returnValue1, BlockParameter blockParameter = null) + { + var pluginsFunction = new PluginsFunction(); + pluginsFunction.ReturnValue1 = returnValue1; + + return ContractHandler.QueryAsync(pluginsFunction, blockParameter); + } + + public Task RemovePluginRequestAsync(RemovePluginFunction removePluginFunction) + { + return ContractHandler.SendRequestAsync(removePluginFunction); + } + + public Task RemovePluginRequestAndWaitForReceiptAsync(RemovePluginFunction removePluginFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(removePluginFunction, cancellationToken); + } + + public Task RemovePluginRequestAsync(string plugin) + { + var removePluginFunction = new RemovePluginFunction(); + removePluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAsync(removePluginFunction); + } + + public Task RemovePluginRequestAndWaitForReceiptAsync(string plugin, CancellationTokenSource cancellationToken = null) + { + var removePluginFunction = new RemovePluginFunction(); + removePluginFunction.Plugin = plugin; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(removePluginFunction, cancellationToken); + } + + public Task SetGovRequestAsync(SetGovFunction setGovFunction) + { + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(SetGovFunction setGovFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SetGovRequestAsync(string gov) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAsync(setGovFunction); + } + + public Task SetGovRequestAndWaitForReceiptAsync(string gov, CancellationTokenSource cancellationToken = null) + { + var setGovFunction = new SetGovFunction(); + setGovFunction.Gov = gov; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(setGovFunction, cancellationToken); + } + + public Task SwapRequestAsync(SwapFunction swapFunction) + { + return ContractHandler.SendRequestAsync(swapFunction); + } + + public Task SwapRequestAndWaitForReceiptAsync(SwapFunction swapFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapFunction, cancellationToken); + } + + public Task SwapRequestAsync(List path, BigInteger amountIn, BigInteger minOut, string receiver) + { + var swapFunction = new SwapFunction(); + swapFunction.Path = path; + swapFunction.AmountIn = amountIn; + swapFunction.MinOut = minOut; + swapFunction.Receiver = receiver; + + return ContractHandler.SendRequestAsync(swapFunction); + } + + public Task SwapRequestAndWaitForReceiptAsync(List path, BigInteger amountIn, BigInteger minOut, string receiver, CancellationTokenSource cancellationToken = null) + { + var swapFunction = new SwapFunction(); + swapFunction.Path = path; + swapFunction.AmountIn = amountIn; + swapFunction.MinOut = minOut; + swapFunction.Receiver = receiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapFunction, cancellationToken); + } + + public Task SwapETHToTokensRequestAsync(SwapETHToTokensFunction swapETHToTokensFunction) + { + return ContractHandler.SendRequestAsync(swapETHToTokensFunction); + } + + public Task SwapETHToTokensRequestAndWaitForReceiptAsync(SwapETHToTokensFunction swapETHToTokensFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapETHToTokensFunction, cancellationToken); + } + + public Task SwapETHToTokensRequestAsync(List path, BigInteger minOut, string receiver) + { + var swapETHToTokensFunction = new SwapETHToTokensFunction(); + swapETHToTokensFunction.Path = path; + swapETHToTokensFunction.MinOut = minOut; + swapETHToTokensFunction.Receiver = receiver; + + return ContractHandler.SendRequestAsync(swapETHToTokensFunction); + } + + public Task SwapETHToTokensRequestAndWaitForReceiptAsync(List path, BigInteger minOut, string receiver, CancellationTokenSource cancellationToken = null) + { + var swapETHToTokensFunction = new SwapETHToTokensFunction(); + swapETHToTokensFunction.Path = path; + swapETHToTokensFunction.MinOut = minOut; + swapETHToTokensFunction.Receiver = receiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapETHToTokensFunction, cancellationToken); + } + + public Task SwapTokensToETHRequestAsync(SwapTokensToETHFunction swapTokensToETHFunction) + { + return ContractHandler.SendRequestAsync(swapTokensToETHFunction); + } + + public Task SwapTokensToETHRequestAndWaitForReceiptAsync(SwapTokensToETHFunction swapTokensToETHFunction, CancellationTokenSource cancellationToken = null) + { + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapTokensToETHFunction, cancellationToken); + } + + public Task SwapTokensToETHRequestAsync(List path, BigInteger amountIn, BigInteger minOut, string receiver) + { + var swapTokensToETHFunction = new SwapTokensToETHFunction(); + swapTokensToETHFunction.Path = path; + swapTokensToETHFunction.AmountIn = amountIn; + swapTokensToETHFunction.MinOut = minOut; + swapTokensToETHFunction.Receiver = receiver; + + return ContractHandler.SendRequestAsync(swapTokensToETHFunction); + } + + public Task SwapTokensToETHRequestAndWaitForReceiptAsync(List path, BigInteger amountIn, BigInteger minOut, string receiver, CancellationTokenSource cancellationToken = null) + { + var swapTokensToETHFunction = new SwapTokensToETHFunction(); + swapTokensToETHFunction.Path = path; + swapTokensToETHFunction.AmountIn = amountIn; + swapTokensToETHFunction.MinOut = minOut; + swapTokensToETHFunction.Receiver = receiver; + + return ContractHandler.SendRequestAndWaitForReceiptAsync(swapTokensToETHFunction, cancellationToken); + } + + public Task UsdgQueryAsync(UsdgFunction usdgFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(usdgFunction, blockParameter); + } + + + public Task UsdgQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task VaultQueryAsync(VaultFunction vaultFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(vaultFunction, blockParameter); + } + + + public Task VaultQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + + public Task WethQueryAsync(WethFunction wethFunction, BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(wethFunction, blockParameter); + } + + + public Task WethQueryAsync(BlockParameter blockParameter = null) + { + return ContractHandler.QueryAsync(null, blockParameter); + } + } +} diff --git a/src/Managing.WebApp/.env b/src/Managing.WebApp/.env new file mode 100644 index 0000000..a673da2 --- /dev/null +++ b/src/Managing.WebApp/.env @@ -0,0 +1,6 @@ +VITE_API_URL_LOCAL=https://localhost:5001 +VITE_API_URL_SERVER=https://localhost +VITE_WORKER_URL_LOCAL=https://localhost:5002 +VITE_WORKER_URL_SERVER=https://localhost:444 +ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9 +WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5 \ No newline at end of file diff --git a/src/Managing.WebApp/.eslintignore b/src/Managing.WebApp/.eslintignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/src/Managing.WebApp/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/src/Managing.WebApp/.eslintrc b/src/Managing.WebApp/.eslintrc new file mode 100644 index 0000000..2d7c78b --- /dev/null +++ b/src/Managing.WebApp/.eslintrc @@ -0,0 +1,98 @@ +{ + "root": true, + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:jsx-a11y/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "jsx-a11y", + "import", + "sort-keys-fix", + "react-hooks", + "@typescript-eslint", + "prettier" + ], + "env": { + "browser": true, + "node": true, + "es6": true, + "jest": true + }, + "globals": { + "JSX": "readonly" + }, + "settings": { + "react": { + "version": "detect" + }, + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + }, + "typescript": { + "alwaysTryTypes": true, + // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` + "project": ["tsconfig.json"] + } + } + }, + "rules": { + "no-alert": "error", + "no-console": "error", + "react-hooks/rules-of-hooks": "error", + "prettier/prettier": [ + "warn", + {}, + { + "properties": { + "usePrettierrc": true + } + } + ], + "import/order": [ + "warn", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object" + ], + "newlines-between": "always", + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], + "import/named": "error", + "import/default": "error", + "import/export": "error", + "import/no-named-as-default": "warn", + "import/no-duplicates": "error", + "sort-keys-fix/sort-keys-fix": "warn", + "@import/no-named-as-default-member": "off", + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-function": "off" + }, + "overrides": [ + { + "files": ["*.js"], + "rules": { + "@typescript-eslint/explicit-module-boundary-types": ["off"], + "@typescript-eslint/no-var-requires": ["off"] + } + } + ] +} diff --git a/src/Managing.WebApp/.gitattributes b/src/Managing.WebApp/.gitattributes new file mode 100644 index 0000000..2dd6f4d --- /dev/null +++ b/src/Managing.WebApp/.gitattributes @@ -0,0 +1,3 @@ +.jest/* linguist-vendored +mocks/* linguist-vendored +mockServiceWorker.js linguist-vendored diff --git a/src/Managing.WebApp/.github/workflows/build.yml b/src/Managing.WebApp/.github/workflows/build.yml new file mode 100644 index 0000000..cf9565c --- /dev/null +++ b/src/Managing.WebApp/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2-beta + with: + node-version: '18.1.0' + - run: yarn install + - run: yarn build diff --git a/src/Managing.WebApp/.github/workflows/lint.yml b/src/Managing.WebApp/.github/workflows/lint.yml new file mode 100644 index 0000000..d84f5b7 --- /dev/null +++ b/src/Managing.WebApp/.github/workflows/lint.yml @@ -0,0 +1,18 @@ +name: Lint +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2-beta + with: + node-version: '18.1.0' + - run: yarn install + - run: yarn lint diff --git a/src/Managing.WebApp/.github/workflows/test.yml b/src/Managing.WebApp/.github/workflows/test.yml new file mode 100644 index 0000000..c409935 --- /dev/null +++ b/src/Managing.WebApp/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: Test +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2-beta + with: + node-version: '18.1.0' + - run: yarn install + - run: yarn test diff --git a/src/Managing.WebApp/.github/workflows/typecheck.yml b/src/Managing.WebApp/.github/workflows/typecheck.yml new file mode 100644 index 0000000..eeb1640 --- /dev/null +++ b/src/Managing.WebApp/.github/workflows/typecheck.yml @@ -0,0 +1,18 @@ +name: Typecheck +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2-beta + with: + node-version: '18.1.0' + - run: yarn install + - run: yarn typecheck diff --git a/src/Managing.WebApp/.gitignore b/src/Managing.WebApp/.gitignore new file mode 100644 index 0000000..d451ff1 --- /dev/null +++ b/src/Managing.WebApp/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/src/Managing.WebApp/.prettierignore b/src/Managing.WebApp/.prettierignore new file mode 100644 index 0000000..4969764 --- /dev/null +++ b/src/Managing.WebApp/.prettierignore @@ -0,0 +1,5 @@ +.git +node_modules +.eslintignore +.gitignore +LICENSE diff --git a/src/Managing.WebApp/.prettierrc b/src/Managing.WebApp/.prettierrc new file mode 100644 index 0000000..fd496a8 --- /dev/null +++ b/src/Managing.WebApp/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/src/Managing.WebApp/Dockerfile b/src/Managing.WebApp/Dockerfile new file mode 100644 index 0000000..414406f --- /dev/null +++ b/src/Managing.WebApp/Dockerfile @@ -0,0 +1,20 @@ +FROM node:18-alpine + +WORKDIR /app + +COPY . /app + +ENV NODE_ENV=production +ENV VITE_API_URL_LOCAL=https://localhost:5001 +ENV VITE_API_URL_SERVER=https://localhost + + +RUN npm install serve -g + +RUN npm install + +RUN npm run build + +EXPOSE 3000 + +CMD ["npm", "run", "serve"] \ No newline at end of file diff --git a/src/Managing.WebApp/LICENSE b/src/Managing.WebApp/LICENSE new file mode 100644 index 0000000..b229c5f --- /dev/null +++ b/src/Managing.WebApp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Managing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/Managing.WebApp/README.md b/src/Managing.WebApp/README.md new file mode 100644 index 0000000..5be74be --- /dev/null +++ b/src/Managing.WebApp/README.md @@ -0,0 +1,94 @@ +# vite-react-ts-extended [![Typecheck](https://github.com/laststance/vite-react-ts-extended/actions/workflows/typecheck.yml/badge.svg)](https://github.com/laststance/vite-react-ts-extended/actions/workflows/typecheck.yml) [![Test](https://github.com/laststance/vite-react-ts-extended/actions/workflows/test.yml/badge.svg)](https://github.com/laststance/vite-react-ts-extended/actions/workflows/test.yml) [![Build](https://github.com/laststance/vite-react-ts-extended/actions/workflows/build.yml/badge.svg)](https://github.com/laststance/vite-react-ts-extended/actions/workflows/build.yml) [![Lint](https://github.com/laststance/vite-react-ts-extended/actions/workflows/lint.yml/badge.svg)](https://github.com/laststance/vite-react-ts-extended/actions/workflows/lint.yml) [![Depfu](https://badges.depfu.com/badges/6c7775918ccc8647160750e168617a65/overview.svg)](https://depfu.com/github/laststance/vite-react-ts-extended?project_id=32682) + +> My CRA alternative. +> Create plain and lightweight React+TS programming environment with familiar pre-setup tooling +> eslint/prettier, jest/TS/react-testing-library/msw, tailwindcss, CI. + +## [Trying this Online!](https://codesandbox.io/s/vite-react-ts-extended-cbgyfz?file=/src/App.tsx) + + + +This is the official [Vite](https://vitejs.dev/) template(`npm init vite@latest myapp -- --template react-ts`) and some extended setup. + +- [eslint-typescript](https://github.com/typescript-eslint/typescript-eslint) and [Prettier](https://prettier.io/) integration. Rules are 100% my personal setup 💅 +- [jest](https://jestjs.io/), [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/), [react-hooks-testing-library](https://github.com/testing-library/react-hooks-testing-library), [MSW](https://mswjs.io/) +- [tailwindcss](https://tailwindcss.com/) +- [Github Actions](https://github.com/features/actions) + +All npm package are keeping least release version powered by [Depfu](https://depfu.com/). + +# Installation + +``` +npx degit laststance/vite-react-ts-extended myapp +``` + +### yarn + +```sh +cd myapp +yarn install +yarn validate # The installation was successful if no error occurs after running 'validate'. +yarn dev +``` + +### npm + +```sh +cd myapp +npm install +npm run validate # The installation was successful if no error occurs after running 'validate'. +npm run dev +``` + +### Commands + +```sh +yarn dev # start development server +yarn validate # run test,lint,build,typecheck concurrently +yarn test # run jest +yarn lint # run eslint +yarn lint:fix # run eslint with --fix option +yarn typecheck # run TypeScript compiler check +yarn build # build production bundle to 'dist' directly +yarn prettier # run prettier for json|yml|css|md|mdx files +yarn clean # remove 'node_modules' 'yarn.lock' 'dist' completely +yarn serve # launch server for production bundle in local +``` + +# Background + +The evolution of the React framework is accelerating more than ever before. +[Next.js](https://nextjs.org/), [Remix](https://remix.run/), [RedwoodJS](https://redwoodjs.com/), [Gatsby](https://www.gatsbyjs.com/), [Blitz](https://blitzjs.com/) etc... + +Ahthough I still need plain React programming starter some reason. (.e.g Demo, Experiment like Deep Dive React Core.) +So far, [create-react-app](https://github.com/facebook/create-react-app) **was** it. +In short, [create-react-app](https://github.com/facebook/create-react-app) development couldn't say active. Please read the [Issue](https://github.com/facebook/create-react-app/issues/11180) in details. + +So I created an alternative to [create-react-app](https://github.com/facebook/create-react-app) for myself, based on [Vite](https://github.com/facebook/create-react-app). +This project contains my very opinionted setup, +but I hope it will be a useful tool for people who have similar needs to mine! 😀 + +# License + +MIT + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + +

ryota-murakami

💻 📖 ⚠️
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/src/Managing.WebApp/index.html b/src/Managing.WebApp/index.html new file mode 100644 index 0000000..c6488fe --- /dev/null +++ b/src/Managing.WebApp/index.html @@ -0,0 +1,16 @@ + + + + + + + Managing + + +
+ + + + diff --git a/src/Managing.WebApp/jest.config.js b/src/Managing.WebApp/jest.config.js new file mode 100644 index 0000000..dd7f4d9 --- /dev/null +++ b/src/Managing.WebApp/jest.config.js @@ -0,0 +1,35 @@ +const config = { + collectCoverageFrom: ['/src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'], + moduleDirectories: ['node_modules'], + moduleFileExtensions: ['js', 'mjs', 'jsx', 'ts', 'tsx', 'json'], + moduleNameMapper: { + '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', + }, + notify: true, + notifyMode: 'success-change', + resetMocks: true, + roots: [''], + setupFilesAfterEnv: ['/jest/setupTests.ts'], + testEnvironment: 'jsdom', + testMatch: [ + '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', + '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', + ], + transform: { + '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': + '/jest/fileTransform.js', + '^.+\\.[jt]sx?$': 'esbuild-jest', + '^.+\\.css$': '/jest/cssTransform.js', + }, + transformIgnorePatterns: [ + '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', + '^.+\\.module\\.(css|sass|scss)$', + ], + verbose: true, + watchPlugins: [ + 'jest-watch-typeahead/filename', + 'jest-watch-typeahead/testname', + ], +} + +module.exports = config diff --git a/src/Managing.WebApp/mockServiceWorker.js b/src/Managing.WebApp/mockServiceWorker.js new file mode 100644 index 0000000..4e18b44 --- /dev/null +++ b/src/Managing.WebApp/mockServiceWorker.js @@ -0,0 +1,338 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (0.36.5). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929' +const bypassHeaderName = 'x-msw-bypass' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + return self.skipWaiting() +}) + +self.addEventListener('activate', async function (event) { + return self.clients.claim() +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll() + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +// Resolve the "main" client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll() + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: serializeHeaders(clonedResponse.headers), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +async function getResponse(event, client, requestId) { + const { request } = event + const requestClone = request.clone() + const getOriginalResponse = () => fetch(requestClone) + + // Bypass mocking when the request client is not active. + if (!client) { + return getOriginalResponse() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return await getOriginalResponse() + } + + // Bypass requests with the explicit bypass header + if (requestClone.headers.get(bypassHeaderName) === 'true') { + const cleanRequestHeaders = serializeHeaders(requestClone.headers) + + // Remove the bypass header to comply with the CORS preflight check. + delete cleanRequestHeaders[bypassHeaderName] + + const originalRequest = new Request(requestClone, { + headers: new Headers(cleanRequestHeaders), + }) + + return fetch(originalRequest) + } + + // Send the request to the client-side MSW. + const reqHeaders = serializeHeaders(request.headers) + const body = await request.text() + + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: reqHeaders, + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body, + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_SUCCESS': { + return delayPromise( + () => respondWithMock(clientMessage), + clientMessage.payload.delay, + ) + } + + case 'MOCK_NOT_FOUND': { + return getOriginalResponse() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload + const networkError = new Error(message) + networkError.name = name + + // Rejecting a request Promise emulates a network error. + throw networkError + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body) + + console.error( + `\ +[MSW] Uncaught exception in the request handler for "%s %s": + +${parsedBody.location} + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ +`, + request.method, + request.url, + ) + + return respondWithMock(clientMessage) + } + } + + return getOriginalResponse() +} + +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = uuidv4() + + return event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +function serializeHeaders(headers) { + const reqHeaders = {} + headers.forEach((value, name) => { + reqHeaders[name] = reqHeaders[name] + ? [].concat(reqHeaders[name]).concat(value) + : value + }) + return reqHeaders +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(JSON.stringify(message), [channel.port2]) + }) +} + +function delayPromise(cb, duration) { + return new Promise((resolve) => { + setTimeout(() => resolve(cb()), duration) + }) +} + +function respondWithMock(clientMessage) { + return new Response(clientMessage.payload.body, { + ...clientMessage.payload, + headers: clientMessage.payload.headers, + }) +} + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} diff --git a/src/Managing.WebApp/package.json b/src/Managing.WebApp/package.json new file mode 100644 index 0000000..39f3f07 --- /dev/null +++ b/src/Managing.WebApp/package.json @@ -0,0 +1,102 @@ +{ + "name": "managing", + "version": "2.0.0", + "license": "MIT", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "serve": "serve -s dist -p 3000", + "test": "jest", + "lint": "eslint . --ext .ts,.tsx,.js,jsx", + "lint:fix": "eslint . --ext .ts,.tsx,.js,jsx --fix", + "typecheck": "tsc --noEmit", + "prettier": "prettier --write \"**/*.+(json|yml|css|md|mdx)\"", + "clean": "rimraf node_modules yarn.lock dist", + "validate": "./scripts/validate" + }, + "dependencies": { + "@heroicons/react": "^1.0.6", + "@microsoft/signalr": "^6.0.5", + "@tanstack/react-query": "^4.33.0", + "@wagmi/chains": "^0.2.9", + "@wagmi/core": "^1.3.9", + "@walletconnect/universal-provider": "^2.8.6", + "axios": "^0.27.2", + "classnames": "^2.3.1", + "connectkit": "^1.1.3", + "date-fns": "^2.30.0", + "jotai": "^1.6.7", + "lightweight-charts": "git+https://github.com/ntf/lightweight-charts.git", + "moment": "^2.29.3", + "plotly.js": "^2.18.1", + "react": "^18.1.0", + "react-cookie": "^4.1.1", + "react-dom": "^18.1.0", + "react-grid-layout": "^1.3.4", + "react-hook-form": "^7.31.2", + "react-hot-toast": "^2.2.0", + "react-icons": "^4.3.1", + "react-iframe": "^1.8.0", + "react-plotly.js": "^2.6.0", + "react-router-dom": "^6.3.0", + "react-slider": "^2.0.1", + "react-table": "^7.8.0", + "react-toastify": "^9.0.1", + "reactflow": "^11.8.3", + "viem": "^1.4.2", + "wagmi": "^1.3.10", + "web3": "^4.0.2", + "zustand": "^4.4.1" + }, + "devDependencies": { + "@tailwindcss/aspect-ratio": "^0.4.0", + "@tailwindcss/forms": "^0.5.1", + "@tailwindcss/line-clamp": "^0.4.0", + "@tailwindcss/typography": "^0.5.2", + "@tanstack/eslint-plugin-query": "^4.34.1", + "@testing-library/dom": "^8.13.0", + "@testing-library/react": "^13.2.0", + "@types/jest": "^27.5.1", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.4", + "@types/react-grid-layout": "^1.3.2", + "@types/react-plotly.js": "^2.6.0", + "@types/react-slider": "^1.3.1", + "@types/react-table": "^7.7.12", + "@types/signalr": "^2.2.37", + "@types/testing-library__jest-dom": "^5.14.3", + "@typescript-eslint/eslint-plugin": "^5.23.0", + "@typescript-eslint/parser": "^5.23.0", + "@vitejs/plugin-react": "^1.3.2", + "all-contributors-cli": "^6.20.0", + "autoprefixer": "^10.4.7", + "daisyui": "^3.5.1", + "esbuild-jest": "^0.4.0", + "eslint": "^8.15.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-typescript": "^3.0.0", + "eslint-import-resolver-typescript": "^2.7.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react-hooks": "^4.5.0", + "eslint-plugin-sort-keys-fix": "^1.1.2", + "identity-obj-proxy": "^3.0.0", + "jest": "^28.0.0", + "jest-watch-typeahead": "^1.1.0", + "node-notifier": "^10.0.1", + "postcss": "^8.4.13", + "prettier": "^2.6.1", + "prettier-plugin-tailwind-css": "^1.5.0", + "rimraf": "^3.0.2", + "serve": "^14.2.0", + "tailwindcss": "^3.0.23", + "typescript": "^5.0.4", + "vite": "^4.4.9", + "whatwg-fetch": "^3.6.2" + }, + "msw": { + "workerDirectory": "" + } +} diff --git a/src/Managing.WebApp/postcss.config.js b/src/Managing.WebApp/postcss.config.js new file mode 100644 index 0000000..6e41d95 --- /dev/null +++ b/src/Managing.WebApp/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + autoprefixer: {}, + tailwindcss: {}, + }, +} diff --git a/src/Managing.WebApp/prettier.config.js b/src/Managing.WebApp/prettier.config.js new file mode 100644 index 0000000..9280030 --- /dev/null +++ b/src/Managing.WebApp/prettier.config.js @@ -0,0 +1,4 @@ +module.exports = { + plugins: [require('prettier-plugin-tailwindcss')], + tailwindConfig: './tailwind.config.js', +} diff --git a/src/Managing.WebApp/scripts/validate b/src/Managing.WebApp/scripts/validate new file mode 100644 index 0000000..738fceb --- /dev/null +++ b/src/Managing.WebApp/scripts/validate @@ -0,0 +1,11 @@ +#!/bin/sh + +npx concurrently \ + --kill-others-on-fail \ + --prefix "[{name}]" \ + --names "test,lint:fix,typecheck,build" \ + --prefix-colors "bgRed.bold.white,bgGreen.bold.white,bgBlue.bold.white,bgMagenta.bold.white" \ + "npm run test --silent -- --watch=false" \ + "npm run lint:fix --silent" \ + "npm run typecheck --silent" \ + "npm run build --silent" diff --git a/src/Managing.WebApp/src/app/index.tsx b/src/Managing.WebApp/src/app/index.tsx new file mode 100644 index 0000000..96e64a1 --- /dev/null +++ b/src/Managing.WebApp/src/app/index.tsx @@ -0,0 +1,13 @@ +import { Auth } from '../pages/authPage/auth' + +import MyRoutes from './routes' + +const App = () => { + return ( + + + + ) +} + +export default App diff --git a/src/Managing.WebApp/src/app/providers/Hubs.tsx b/src/Managing.WebApp/src/app/providers/Hubs.tsx new file mode 100644 index 0000000..f837f26 --- /dev/null +++ b/src/Managing.WebApp/src/app/providers/Hubs.tsx @@ -0,0 +1,18 @@ +import type { HubConnection } from '@microsoft/signalr' +import { HubConnectionBuilder } from '@microsoft/signalr' +// https://www.abrahamberg.com/blog/aspnet-signalr-and-react/ +export class Hub { + public hub: HubConnection + constructor(name: string, baseUrl: string) { + this.hub = new HubConnectionBuilder() + .withUrl(baseUrl + '/' + name) + .withAutomaticReconnect() + .build() + try { + this.hub.start() + } catch (err) { + // eslint-disable-next-line no-console + console.log(err) + } + } +} diff --git a/src/Managing.WebApp/src/app/routes/index.tsx b/src/Managing.WebApp/src/app/routes/index.tsx new file mode 100644 index 0000000..74bcb4f --- /dev/null +++ b/src/Managing.WebApp/src/app/routes/index.tsx @@ -0,0 +1,118 @@ +import { Suspense, lazy } from 'react' +import { Route, Routes } from 'react-router-dom' + +import LayoutMain from '../../layouts' +import Desk from '../../pages/desk/desk' +import Scenario from '../../pages/scenarioPage/scenario' +import Tools from '../../pages/toolsPage/tools' +import Workflows from '../../pages/workflow/workflows' + +const Backtest = lazy(() => import('../../pages/backtestPage/backtest')) +const Bots = lazy(() => import('../../pages/botsPage/bots')) +const Dashboard = lazy(() => import('../../pages/dashboardPage/dashboard')) +const Settings = lazy(() => import('../../pages/settingsPage/settings')) + +const MyRoutes = () => { + return ( + + }> + + + + } + /> + + + }> + + + + } + /> + + + }> + + + + } + /> + + + }> + + + + } + /> + + + }> + + + + } + /> + + + }> + + + + } + /> + + }> + + + + } + /> + + + }> + + + + } + /> + + {/* }> + + + + } + /> + */} + + ) +} + +export default MyRoutes diff --git a/src/Managing.WebApp/src/app/store/accountStore.tsx b/src/Managing.WebApp/src/app/store/accountStore.tsx new file mode 100644 index 0000000..b264119 --- /dev/null +++ b/src/Managing.WebApp/src/app/store/accountStore.tsx @@ -0,0 +1,22 @@ +import { create } from 'zustand' + +import type { AccountStore } from '../../global/type' + +export const useAuthStore = create((set) => ({ + accounts: [], + onInitialize: () => { + console.log('useFlowStore onInitialize') + + // const accountClient = new AccountClient({}, apiUrl) + // const accounts = await accountClient.account_GetAccounts() + // if (accounts.length > 0) { + // get().setAccounts(accounts) + // } + }, + setAccounts: (accounts) => { + set((state) => ({ + ...state, + accounts: accounts, + })) + }, +})) diff --git a/src/Managing.WebApp/src/app/store/apiStore.tsx b/src/Managing.WebApp/src/app/store/apiStore.tsx new file mode 100644 index 0000000..632fb70 --- /dev/null +++ b/src/Managing.WebApp/src/app/store/apiStore.tsx @@ -0,0 +1,28 @@ +import create from 'zustand' + +type ApiStore = { + isProd: boolean + apiUrl: string + workerUrl: string + toggleApiUrl: () => void +} + +const useApiUrlStore = create((set) => ({ + // Mettez la valeur initiale de isProd ici + apiUrl: import.meta.env.VITE_API_URL_SERVER, + isProd: true, + toggleApiUrl: () => { + set((state) => ({ + apiUrl: state.isProd + ? import.meta.env.VITE_API_URL_LOCAL + : import.meta.env.VITE_API_URL_SERVER, + isProd: !state.isProd, + workerUrl: state.isProd + ? import.meta.env.VITE_WORKER_URL_LOCAL + : import.meta.env.VITE_WORKER_URL_SERVER, + })) + }, + workerUrl: import.meta.env.VITE_WORKER_URL_SERVER, +})) + +export default useApiUrlStore diff --git a/src/Managing.WebApp/src/app/store/flowStore.tsx b/src/Managing.WebApp/src/app/store/flowStore.tsx new file mode 100644 index 0000000..f176552 --- /dev/null +++ b/src/Managing.WebApp/src/app/store/flowStore.tsx @@ -0,0 +1,28 @@ +import { create } from 'zustand' + +import type { IFlow } from '../../generated/ManagingApi' +import { WorkflowClient } from '../../generated/ManagingApi' + +type FlowStore = { + setFlows: (flows: IFlow[]) => void + getFlows: (apiUrl: string) => void + flows: IFlow[] +} + +export const useFlowStore = create((set) => ({ + flows: [] as IFlow[], + getFlows: async (apiUrl) => { + const client = new WorkflowClient({}, apiUrl) + await client.workflow_GetAvailableFlows().then((data) => { + set(() => ({ + flows: data, + })) + }) + }, + setFlows: (flows) => { + set((state) => ({ + ...state, + flows: flows, + })) + }, +})) diff --git a/src/Managing.WebApp/src/app/store/selectors/workflowSelector.tsx b/src/Managing.WebApp/src/app/store/selectors/workflowSelector.tsx new file mode 100644 index 0000000..5f8f754 --- /dev/null +++ b/src/Managing.WebApp/src/app/store/selectors/workflowSelector.tsx @@ -0,0 +1,13 @@ +import type { IWorkflowStore } from '../workflowStore' + +export const WorkflowSelector = (state: IWorkflowStore) => ({ + edges: state.edges, + initWorkFlow: state.initWorkFlow, + nodes: state.nodes, + onConnect: state.onConnect, + onEdgesChange: state.onEdgesChange, + onNodesChange: state.onNodesChange, + resetWorkflow: state.resetWorkflow, + setNodes: state.setNodes, + updateNodeData: state.updateNodeData, +}) diff --git a/src/Managing.WebApp/src/app/store/workflowStore.tsx b/src/Managing.WebApp/src/app/store/workflowStore.tsx new file mode 100644 index 0000000..834b72a --- /dev/null +++ b/src/Managing.WebApp/src/app/store/workflowStore.tsx @@ -0,0 +1,113 @@ +import type { + Connection, + Edge, + EdgeChange, + Node, + NodeChange, + OnNodesChange, + OnEdgesChange, + OnConnect, +} from 'reactflow' +import { addEdge, applyNodeChanges, applyEdgeChanges } from 'reactflow' +import { create } from 'zustand' + +import type { + SyntheticFlowParameter, + FlowParameter, + IFlow, +} from '../../generated/ManagingApi' + +export type IWorkflowStore = { + nodes: Node[] + initialNodes: Node[] + edges: Edge[] + initialEdges: Edge[] + onNodesChange: OnNodesChange + onEdgesChange: OnEdgesChange + onConnect: OnConnect + updateNodeData: (nodeId: string, parameterName: string, value: string) => void + initWorkFlow: (nodes: Node[], edges: Edge[]) => void + setNodes: (nodes: Node[]) => void + resetWorkflow: () => void +} + +// this is our useStore hook that we can use in our components to get parts of the store and call actions +const useWorkflowStore = create((set, get) => ({ + edges: [], + initWorkFlow: (nodes: Node[], edges: Edge[]) => { + set({ + edges: edges, + initialEdges: edges, + initialNodes: nodes, + nodes: nodes, + }) + }, + initialEdges: [], + initialNodes: [], + nodes: [], + onConnect: (connection: Connection, callback: void) => { + set({ + edges: addEdge(connection, get().edges), + }) + }, + onEdgesChange: (changes: EdgeChange[]) => { + set({ + edges: applyEdgeChanges(changes, get().edges), + }) + }, + onNodesChange: (changes: NodeChange[]) => { + set({ + nodes: applyNodeChanges(changes, get().nodes), + }) + }, + resetWorkflow: () => { + set({ + edges: get().initialEdges, + initialEdges: get().initialEdges, + initialNodes: get().initialNodes, + nodes: get().initialNodes, + }) + }, + setNodes: (nodes: Node[]) => { + set({ + nodes: nodes, + }) + }, + updateNodeData: (nodeId: string, parameterName: string, value: string) => { + set({ + nodes: get().nodes.map((node) => { + if (node.id === nodeId) { + node.data.parameters = updateParameters( + node.data.parameters, + parameterName, + value + ) + } + return node + }), + }) + }, +})) + +const updateParameters = ( + parameters: FlowParameter[], + name: string, + value: string +) => { + if (!parameters.find((parameter) => parameter.name === name)) { + parameters.push({ + name: name, + value: value, + } as SyntheticFlowParameter) + } else { + parameters = parameters.map((parameter) => { + if (parameter.name === name) { + parameter.value = value + } + return parameter + }) + } + return parameters +} + +export default useWorkflowStore diff --git a/src/Managing.WebApp/src/assets/img/logo.png b/src/Managing.WebApp/src/assets/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c4ccd58aeb4f358143206b57d4390129e29da723 GIT binary patch literal 33041 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 ( +

  • {prop.children}
  • +) + +export default List diff --git a/src/Managing.WebApp/src/components/atoms/Loader/Loader.tsx b/src/Managing.WebApp/src/components/atoms/Loader/Loader.tsx new file mode 100644 index 0000000..3ddfea1 --- /dev/null +++ b/src/Managing.WebApp/src/components/atoms/Loader/Loader.tsx @@ -0,0 +1,16 @@ +import type { ILoader } from '../../../global/type' + +const defaultClasses = 'loading loading-ring ' + +const loaderSize = { + lg: defaultClasses + 'loading-lg', + md: defaultClasses + 'loading-md', + sm: defaultClasses + 'loading-sm', + xs: defaultClasses + 'loading-xs', +} + +const Loader = ({ size = 'md' }: ILoader) => { + return +} + +export default Loader diff --git a/src/Managing.WebApp/src/components/atoms/MyLink/MyLink.tsx b/src/Managing.WebApp/src/components/atoms/MyLink/MyLink.tsx new file mode 100644 index 0000000..c979720 --- /dev/null +++ b/src/Managing.WebApp/src/components/atoms/MyLink/MyLink.tsx @@ -0,0 +1,42 @@ +import { Link, NavLink } from 'react-router-dom' + +import type { IMyLinkProps } from '../../../global/type' + +const MyLink = ({ + children, + href, + as: asof = 'link', + ...props +}: IMyLinkProps): JSX.Element => { + const navLink = asof === 'navlink' + const onlyLink = asof === 'link' + + if (navLink) { + return ( + + {children} + + ) + } + + if (onlyLink) { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) +} + +export default MyLink diff --git a/src/Managing.WebApp/src/components/atoms/Select/Select.tsx b/src/Managing.WebApp/src/components/atoms/Select/Select.tsx new file mode 100644 index 0000000..f31dcf8 --- /dev/null +++ b/src/Managing.WebApp/src/components/atoms/Select/Select.tsx @@ -0,0 +1,7 @@ +import type { IListProp } from '../../../global/type' + +const Select = (prop: IListProp): JSX.Element => ( + +) + +export default Select diff --git a/src/Managing.WebApp/src/components/atoms/Slider/Slider.tsx b/src/Managing.WebApp/src/components/atoms/Slider/Slider.tsx new file mode 100644 index 0000000..a19e1e8 --- /dev/null +++ b/src/Managing.WebApp/src/components/atoms/Slider/Slider.tsx @@ -0,0 +1,30 @@ +import type { FunctionComponent } from 'react' + +import type { IPropsComponent } from '../../../global/type' + +const Slider: FunctionComponent = (props: IPropsComponent) => { + return ( + <> +
    + +
    +
    + {props.prefixValue} + {props.value} + {props.suffixValue} +
    + + ) +} + +export default Slider diff --git a/src/Managing.WebApp/src/components/atoms/index.tsx b/src/Managing.WebApp/src/components/atoms/index.tsx new file mode 100644 index 0000000..77ced26 --- /dev/null +++ b/src/Managing.WebApp/src/components/atoms/index.tsx @@ -0,0 +1,5 @@ +export { default as List } from './List/List' +export { default as MyLink } from './MyLink/MyLink' +export { default as Select } from './Select/Select' +export { default as Slider } from './Slider/Slider' +export { default as Loader } from './Loader/Loader' diff --git a/src/Managing.WebApp/src/components/mollecules/Card/Card.tsx b/src/Managing.WebApp/src/components/mollecules/Card/Card.tsx new file mode 100644 index 0000000..7c5c677 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Card/Card.tsx @@ -0,0 +1,45 @@ +import type { CardProps } from '../../../global/type' + +const Card = ({ name, children, showCloseButton, info }: CardProps) => { + return ( +
    +
    +
    + {name} + {info && ( +
    + i +
    + )} +
    + +
    + {showCloseButton && ( + + )} +
    +
    +
    {children}
    +
    + ) +} + +export default Card diff --git a/src/Managing.WebApp/src/components/mollecules/CardText/CardText.tsx b/src/Managing.WebApp/src/components/mollecules/CardText/CardText.tsx new file mode 100644 index 0000000..64f51a3 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/CardText/CardText.tsx @@ -0,0 +1,77 @@ +import ArrowDownIcon from '@heroicons/react/solid/ArrowDownIcon' +import ArrowUpIcon from '@heroicons/react/solid/ArrowUpIcon' + +import { Position, TradeDirection } from '../../../generated/ManagingApi' +import type { ICardPosition, ICardSignal, ICardText } from '../../../global/type' + +function getItemTextHeaderClass() { + return 'text-xs ' +} +function getItemTextValueClass() { + return 'text-md ' +} + +export function CardText({ title, content }: ICardText) { + return ( +
    +

    {title}

    +

    {content}

    +
    + ) +} + +export function CardPosition({ positions, positivePosition }: ICardPosition) { + return ( + <> +
    +

    + {positivePosition ? 'Winning position' : 'Losing position'} +

    +

    + { + positions.filter((p: Position) => p.originDirection == TradeDirection.Long) + .length + }{' '} + {' '} + { + positions.filter((p) => p.originDirection == TradeDirection.Short) + .length + }{' '} + +

    +
    + + ) +} + +export function CardSignal({ signals }: ICardSignal) { + return ( + <> +
    +

    Signals

    +

    + {signals.filter((p) => p.direction == TradeDirection.Long).length}{' '} + {' '} + {signals.filter((p) => p.direction == TradeDirection.Short).length}{' '} + +

    +
    + + ) +} diff --git a/src/Managing.WebApp/src/components/mollecules/FormInput/FormInput.tsx b/src/Managing.WebApp/src/components/mollecules/FormInput/FormInput.tsx new file mode 100644 index 0000000..beb18c1 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/FormInput/FormInput.tsx @@ -0,0 +1,24 @@ +import React from 'react' + +import type { IFormInput } from '../../../global/type' + +const FormInput: React.FC = ({ + children, + label, + htmlFor, + inline = false, +}) => { + const groupStyle = inline ? 'flex-wrap' : '' + return ( +
    +
    + + {children} +
    +
    + ) +} + +export default FormInput diff --git a/src/Managing.WebApp/src/components/mollecules/GridTile/GridTile.tsx b/src/Managing.WebApp/src/components/mollecules/GridTile/GridTile.tsx new file mode 100644 index 0000000..a59d89f --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/GridTile/GridTile.tsx @@ -0,0 +1,38 @@ +import type { IGridTile } from '../../../global/type' + +const GridTile = ({ children, title }: IGridTile) => { + return ( +
    +
    +
    + {title} +
    + +
    + +
    +
    +
    {children}
    +
    + ) +} + +export default GridTile diff --git a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx new file mode 100644 index 0000000..b80ff7a --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx @@ -0,0 +1,101 @@ +import { StatusOfflineIcon } from '@heroicons/react/solid' +import type { SubmitHandler } from 'react-hook-form' +import { useForm } from 'react-hook-form' +import { useAccount, useDisconnect, useSignMessage } from 'wagmi' + +import useApiUrlStore from '../../../app/store/apiStore' +import { UserClient } from '../../../generated/ManagingApi' +import type { ILoginFormInput } from '../../../global/type' +import useCookie from '../../../hooks/useCookie' +import { SecondaryNavbar } from '../NavBar/NavBar' + +const LogIn = () => { + const { apiUrl } = useApiUrlStore() + const { register, handleSubmit } = useForm() + const { disconnect } = useDisconnect() + const { address } = useAccount() + const { isLoading, signMessageAsync } = useSignMessage({}) + const { setCookie } = useCookie() + + const onSubmit: SubmitHandler = async (form) => { + const message = 'wagmi' + const signature = await signMessageAsync({ message }) + + if (signature && address) { + const userClient = new UserClient({}, apiUrl) + await userClient + .user_CreateToken({ + address: address.toString(), + message: message, + name: form.name, + signature: signature, + }) + .then((data) => { + setCookie('token', data, 1) + location.reload() + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error(err) + }) + } else { + } + } + + return ( + <> +
    + +
    + +
    +
    +
    +
    +

    + Login +

    +
    +
    + + +
    + + +
    +
    +
    +
    +
    + + ) +} + +export default LogIn diff --git a/src/Managing.WebApp/src/components/mollecules/LogIn/Profile.tsx b/src/Managing.WebApp/src/components/mollecules/LogIn/Profile.tsx new file mode 100644 index 0000000..06f3d42 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/LogIn/Profile.tsx @@ -0,0 +1,26 @@ +import { useConnect } from 'wagmi' + +export function Profile() { + const { connect, connectors, error, isLoading, pendingConnector } = + useConnect() + + return ( +
    + {connectors.map((connector) => ( + + ))} + + {error &&
    {error.message}
    } +
    + ) +} diff --git a/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx b/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx new file mode 100644 index 0000000..752bc3b --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Modal/Modal.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +import type { IModalProps } from '../../../global/type' + +import ModalHeader from './ModalHeader' + +const Modal: React.FC = ({ + showModal, + onSubmit, + onClose, + titleHeader, + children, +}) => { + return ( +
    + {showModal ? ( +
    +
    +
    + + {children} +
    +
    +
    + ) : null} +
    + ) +} + +export default Modal diff --git a/src/Managing.WebApp/src/components/mollecules/Modal/ModalHeader.tsx b/src/Managing.WebApp/src/components/mollecules/Modal/ModalHeader.tsx new file mode 100644 index 0000000..688ff90 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Modal/ModalHeader.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +import type { IModalProps } from '../../../global/type' + +const ModalHeader: React.FC = ({ onClose, titleHeader }: any) => { + return ( +
    + +
    {titleHeader}
    +
    + ) +} + +export default ModalHeader diff --git a/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx new file mode 100644 index 0000000..fd8bc49 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx @@ -0,0 +1,130 @@ +import { useIsFetching } from '@tanstack/react-query' +import { ConnectKitButton } from 'connectkit' +import type { ReactNode } from 'react' +import { useState } from 'react' +import { Link } from 'react-router-dom' + +import { NavItem } from '..' +import useApiUrlStore from '../../../app/store/apiStore' +import Logo from '../../../assets/img/logo.png' +import { Loader } from '../../atoms' + +const navigation = [ + { href: '/desk', name: 'Desk' }, + { href: '/bots', name: 'Bots' }, + { href: '/workflow', name: 'Workflows' }, + { href: '/scenarios', name: 'Scenarios' }, + { href: '/backtest', name: 'Backtest' }, + { href: '/tools', name: 'Tools' }, + { href: '/settings', name: 'Settings' }, +] + +function navItems(isMobile = false) { + return navigation.map((item) => ( + + {item.name} + + )) +} + +function PrimaryNavbar() { + return ( +
    + + logo + + {/* */} +
    {navItems()}
    +
    + ) +} + +const GlobalLoader = () => { + const isFetching = useIsFetching() + return isFetching ? : null +} + +export function SecondaryNavbar() { + const { toggleApiUrl, isProd } = useApiUrlStore() + + return ( +
    + +
    + +
    + +
    + ) +} + +type MobileMenuButtonProps = { + onClick: VoidFunction +} + +function MobileMenuButton({ onClick }: MobileMenuButtonProps) { + return ( +
    + +
    + ) +} +type MobileMenuProps = { + isOpen: boolean +} + +function MobileMenu({ isOpen }: MobileMenuProps) { + return ( +
    +
      {navItems(true)}
    +
    + ) +} +type NavContainerProps = { + children: ReactNode + isMenuOpen: boolean +} + +function NavContainer({ children, isMenuOpen }: NavContainerProps) { + return ( + + ) +} +export default function NavBar() { + const [isMenuOpen, setIsMenuOpen] = useState(false) + + return ( + + + + setIsMenuOpen(!isMenuOpen)} /> + + ) +} diff --git a/src/Managing.WebApp/src/components/mollecules/NavItem/NavItem.tsx b/src/Managing.WebApp/src/components/mollecules/NavItem/NavItem.tsx new file mode 100644 index 0000000..a492973 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/NavItem/NavItem.tsx @@ -0,0 +1,33 @@ +import { NavLink } from 'react-router-dom' + +import type { INavItemProps } from '../../../global/interface' + +function navLinkClasses(isActive: boolean, isMobile: boolean) { + let commonClasses = 'block text-sm px-2 py-4' + if (isMobile) { + return `${commonClasses} ${ + isActive + ? 'text-base-content bg-primary font-semibold' + : 'hover:bg-primary transition duration-300' + }` + } + commonClasses = + 'py-4 px-2 font-semibold hover:text-primary transition duration-300' + return `${commonClasses} ${isActive ? 'text-primary' : 'text-base-content'}` +} + +export default function NavItem({ + children, + href, + isMobile = false, +}: INavItemProps) { + const item = ( + navLinkClasses(isActive, isMobile)} + > + {children} + + ) + return isMobile ?
  • {item}
  • : item +} diff --git a/src/Managing.WebApp/src/components/mollecules/PieChart/PieChart.tsx b/src/Managing.WebApp/src/components/mollecules/PieChart/PieChart.tsx new file mode 100644 index 0000000..51c6c3b --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/PieChart/PieChart.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import Plot from 'react-plotly.js' + +type IPieChart = { + data: number[] + labels: string[] + colors: string[] +} + +const PieChart: React.FC = ({ data, labels, colors }) => { + return ( + <> + + + ) +} + +export default PieChart diff --git a/src/Managing.WebApp/src/components/mollecules/Table/SelectColumnFilter.tsx b/src/Managing.WebApp/src/components/mollecules/Table/SelectColumnFilter.tsx new file mode 100644 index 0000000..f011b60 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Table/SelectColumnFilter.tsx @@ -0,0 +1,36 @@ +// This is a custom filter UI for selecting + +import React from 'react' + +// a unique option from a list +export default function SelectColumnFilter({ + column: { filterValue, setFilter, preFilteredRows, id }, +}: any) { + // Calculate the options for filtering + // using the preFilteredRows + const options = React.useMemo(() => { + const options = new Set() + preFilteredRows.forEach((row: any) => { + options.add(row.values[id]) + }) + return [...options.values()] + }, [id, preFilteredRows]) + + // Render a multi-select box + return ( + + ) +} diff --git a/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx b/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx new file mode 100644 index 0000000..c562ad9 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Table/Table.tsx @@ -0,0 +1,231 @@ +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' +import React from 'react' +import { + useTable, + usePagination, + useSortBy, + useFilters, + useExpanded, +} from 'react-table' + +import type { TableInstanceWithHooks } from '../../../global/type' + +// Define a default UI for filtering +function DefaultColumnFilter({ + column: { filterValue, preFilteredRows, setFilter }, +}: any) { + const count = preFilteredRows.length + + return ( + { + setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely + }} + placeholder={`Search ${count} records...`} + /> + ) +} + +export default function Table({ + columns, + data, + renderRowSubCompontent, + showPagination, + hiddenColumns, + showTotal = false, +}: any) { + const defaultColumn = React.useMemo( + () => ({ + // Let's set up our default Filter UI + Filter: DefaultColumnFilter, + }), + [] + ) + // Use the state and functions returned from useTable to build your UI + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + visibleColumns, + page, // Instead of using 'rows', we'll use page, + // which has only the rows for the active page + + // The rest of these things are super handy, too ;) + canPreviousPage, + canNextPage, + pageOptions, + pageCount, + gotoPage, + nextPage, + previousPage, + setPageSize, + state: { pageIndex, pageSize }, + } = useTable( + { + columns, + data, + defaultColumn, // Be sure to pass the defaultColumn option + initialState: { + hiddenColumns: hiddenColumns ? hiddenColumns : [], + }, + }, + useFilters, + useSortBy, + useExpanded, + usePagination + ) as TableInstanceWithHooks + + // Calculez le total des valeurs dans la colonne USD + const total = data + ? data + .reduce((sum: number, row: any) => { + return sum + (row.value || 0) // Si la valeur est undefined = 0 + }, 0) + .toFixed(2) + ' $' + : '0.00 $' + + // Render the UI for your table + return ( + <> +
    + + + {headerGroups.map((headerGroup: any) => ( + + {headerGroup.headers.map((column: any) => ( + + ))} + + ))} + + + {page.map((row: any) => { + prepareRow(row) + return ( + <> + + {row.cells.map((cell: any) => { + return ( + + ) + })} + + {row.isExpanded ? ( + + + + ) : null} + + ) + })} + + {/* Afficher le total ici */} + {showTotal ? ( + + + + ) : null} +
    +

    + {column.render('Header')} +

    + + {column.isSorted ? ( + column.isSortedDesc ? ( + + ) : ( + + ) + ) : ( + '' + )} + +
    + {column.canFilter ? column.render('Filter') : null} +
    +
    {cell.render('Cell')}
    + {/* + Inside it, call our renderRowSubComponent function. In reality, + you could pass whatever you want as props to + a component like this, including the entire + table instance. But for this example, we'll just + pass the row + */} + {renderRowSubCompontent({ row })} +
    + Total: {total} +
    +
    + {/* + Pagination can be built however you'd like. + This is just a very basic UI implementation: + */} +
    + {showPagination ? ( +
    + {' '} + {' '} + {' '} + {' '} + + Page{' '} + + {pageIndex + 1} of {pageOptions.length} + {' '} + + {/* + | Go to page:{' '} + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + gotoPage(page) + }} + style={{ width: '100px' }} + /> + {' '} */} + +
    + ) : null} + + ) +} diff --git a/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx b/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx new file mode 100644 index 0000000..1f06e88 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Tabs/Tabs.tsx @@ -0,0 +1,77 @@ +import type { FC } from 'react' + +import type { ITabsProps } from '../../../global/type' + +/** + * Avalible Props + * @param className string + * @param tab Array of object + * @param selectedTab number + * @param onClick Function to set the active tab + * @param orientation Tab orientation Vertical | Horizontal + */ +const Tabs: FC = ({ + className = 'tabs-component', + tabs = [], + selectedTab = 0, + onClick, + orientation = 'horizontal', + addButton = false, + onAddButton, +}) => { + const Panel = tabs && tabs.find((tab) => tab.index === selectedTab) + + return ( +
    +
    + {tabs.map((tab) => ( + + ))} + {addButton && ( + + )} +
    +
    + {Panel && ( + + )} +
    +
    + ) +} +export default Tabs diff --git a/src/Managing.WebApp/src/components/mollecules/ThemeSelector/ThemeSelector.tsx b/src/Managing.WebApp/src/components/mollecules/ThemeSelector/ThemeSelector.tsx new file mode 100644 index 0000000..a526acf --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/ThemeSelector/ThemeSelector.tsx @@ -0,0 +1,21 @@ +import useTheme from '../../../hooks/useTheme' +const themes = ['black', 'coffee', 'cyberpunk', 'lofi', 'retro'] + +const ThemeSelector = (): JSX.Element => { + const { setTheme } = useTheme() + + return ( + + ) +} + +export default ThemeSelector diff --git a/src/Managing.WebApp/src/components/mollecules/Toast/Toast.tsx b/src/Managing.WebApp/src/components/mollecules/Toast/Toast.tsx new file mode 100644 index 0000000..2a6c044 --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/Toast/Toast.tsx @@ -0,0 +1,31 @@ +import type { Id, TypeOptions, UpdateOptions } from 'react-toastify' +import { toast } from 'react-toastify' + +const baseOptions: UpdateOptions = { + autoClose: 5000, + closeOnClick: true, + draggable: true, + hideProgressBar: false, + isLoading: false, + position: 'top-right', + progress: undefined, + theme: 'dark', +} + +class Toast { + private id: Id + + constructor(content: string) { + this.id = toast.loading(content) + } + + update(type: TypeOptions, content: string, opts?: any) { + const options = { ...baseOptions, ...opts } + options.type = type + options.render = content + options.isLoading = false + + toast.update(this.id, options) + } +} +export default Toast diff --git a/src/Managing.WebApp/src/components/mollecules/index.tsx b/src/Managing.WebApp/src/components/mollecules/index.tsx new file mode 100644 index 0000000..15228eb --- /dev/null +++ b/src/Managing.WebApp/src/components/mollecules/index.tsx @@ -0,0 +1,14 @@ +export { CardText, CardPosition, CardSignal } from './CardText/CardText' +export { default as NavItem } from './NavItem/NavItem' +export { default as Tabs } from './Tabs/Tabs' +export { default as Modal } from './Modal/Modal' +export { default as Toast } from './Toast/Toast' +export { default as ThemeSelector } from './ThemeSelector/ThemeSelector' +export { default as Table } from './Table/Table' +export { default as NavBar } from './NavBar/NavBar' +export { default as PieChart } from './PieChart/PieChart' +export { default as FormInput } from './FormInput/FormInput' +export { default as LogIn } from './LogIn/LogIn' +export { default as GridTile } from './GridTile/GridTile' +export { default as SelectColumnFilter } from './Table/SelectColumnFilter' +export { default as Card } from './Card/Card' diff --git a/src/Managing.WebApp/src/components/organism/Account/Account.tsx b/src/Managing.WebApp/src/components/organism/Account/Account.tsx new file mode 100644 index 0000000..0fe15c4 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Account/Account.tsx @@ -0,0 +1,13 @@ +import { useAccount, useEnsName } from 'wagmi' + +export function Account() { + const { address } = useAccount() + const { data: ensName } = useEnsName({ address }) + + return ( +
    + {ensName ?? address?.slice(0, -35)} + {ensName ? ` (${address})` : null} +
    + ) +} diff --git a/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx b/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx new file mode 100644 index 0000000..4814f8a --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/ActiveBots/ActiveBots.tsx @@ -0,0 +1,211 @@ +import { + ArrowDownIcon, + ArrowUpIcon, + ChevronDownIcon, + ChevronRightIcon, + PlayIcon, +} from '@heroicons/react/solid' +import React, { useEffect, useState } from 'react' + +import { Hub } from '../../../app/providers/Hubs' +import useApiUrlStore from '../../../app/store/apiStore' +import type { Account, TradingBot } from '../../../generated/ManagingApi' +import { + AccountClient, + BotClient, + TradeDirection, + TradeStatus, +} from '../../../generated/ManagingApi' +import { SelectColumnFilter, Table } from '../../mollecules' +import BacktestRowDetails from '../Backtest/backtestRowDetails' +import StatusBadge from '../StatusBadge/StatusBadge' +import Summary from '../Trading/Summary' + +export default function ActiveBots() { + const [bots, setBots] = useState([]) + const [accounts, setAccounts] = useState([]) + const { apiUrl } = useApiUrlStore() + + const columns = React.useMemo( + () => [ + { + Cell: ({ row }: any) => ( + // Use Cell to render an expander for each row. + // We can use the getToggleRowExpandedProps prop-getter + // to build the expander. + + {row.isExpanded ? ( + + ) : ( + + )} + + ), + + // Make sure it has an ID + Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }: any) => ( + + {isAllRowsExpanded ? 'v' : '>'} + + ), + // Build our expander column + id: 'expander', + }, + { + Cell: ({ cell }: any) => ( + <> + + + ), + Header: 'Status', + accessor: 'status', + disableFilters: true, + sortType: 'basic', + }, + { + accessor: 'isForWatchingOnly', + }, + { + Filter: SelectColumnFilter, + Header: 'Ticker', + accessor: 'ticker', + disableSortBy: true, + }, + { + Header: 'Account', + accessor: 'accountName', + }, + { + Filter: SelectColumnFilter, + Header: 'Timeframe', + accessor: 'timeframe', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Scenario', + accessor: 'scenario', + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <> + { + <> + { + cell.row.values.positions.filter( + (p: any) => p.originDirection == TradeDirection.Long + ).length + }{' '} + + {' | '} + { + cell.row.values.positions.filter( + (p: any) => p.originDirection == TradeDirection.Short + ).length + }{' '} + {' '} + { + cell.row.values.positions.filter( + (p: any) => p.status == TradeStatus.Filled + ).length + }{' '} + {' '} + + } + + ), + Header: 'Positions', + accessor: 'positions', + disableFilters: true, + }, + { + Cell: ({ cell }) => <>{cell.row.values.winRate} %, + Header: 'Winrate', + accessor: 'winRate', + disableFilters: true, + }, + { + Cell: ({ cell }) => <>{cell.row.values.profitAndLoss} $, + Header: 'PNL', + accessor: 'profitAndLoss', + disableFilters: true, + }, + ], + [] + ) + + useEffect(() => { + setupHubConnection().then(() => { + if (bots.length == 0) { + const client = new BotClient({}, apiUrl) + client.bot_GetActiveBots().then((data) => { + setBots(data) + }) + } + }) + const client = new AccountClient({}, apiUrl) + client.account_GetAccounts().then((data) => { + setAccounts(data) + }) + }, []) + + const setupHubConnection = async () => { + const hub = new Hub('bothub', apiUrl).hub + + hub.on('BotsSubscription', (data: TradingBot[]) => { + // eslint-disable-next-line no-console + console.log( + 'bot List', + bots.map((bot) => { + return bot.name + }) + ) + setBots(data) + }) + + return hub + } + + const renderRowSubComponent = React.useCallback( + ({ row }: any) => ( + <> + + + ), + [] + ) + + return ( + <> +
    + +
    +
    + + + + ) +} diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx new file mode 100644 index 0000000..4bb97e0 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx @@ -0,0 +1,309 @@ +import { DotsVerticalIcon, TrashIcon } from '@heroicons/react/solid' +import moment from 'moment' +import React from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { + Backtest, + MoneyManagement, + StartBotRequest, + Ticker, +} from '../../../generated/ManagingApi' +import { + BacktestClient, + BotClient, + BotType, +} from '../../../generated/ManagingApi' +import type { IBacktestCards } from '../../../global/type' +import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal' +import { CardPosition, CardText, Toast } from '../../mollecules' +import CardPositionItem from '../Trading/CardPositionItem' +import TradeChart from '../Trading/TradeChart/TradeChart' + +function baseBadgeClass(isOutlined = false) { + let classes = 'text-xs badge ' + + if (isOutlined) { + classes += 'badge-outline ' + } + return classes +} + +function botStatusResult( + growthPercentage: number | undefined, + hodlPercentage: number | undefined +) { + if (growthPercentage != undefined && hodlPercentage != undefined) { + const isWinning = growthPercentage > hodlPercentage + const classes = + baseBadgeClass() + (isWinning ? 'badge-success' : 'badge-content') + return
    {isWinning ? 'Winning' : 'Losing'}
    + } +} + +// function that return the number of day between a date and today +function daysBetween(date: Date) { + const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds + const firstDate = new Date(date) + const secondDate = new Date() + const diffDays = Math.round( + Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay) + ) + return diffDays +} + +const BacktestCards: React.FC = ({ list, setBacktests }) => { + const { apiUrl } = useApiUrlStore() + const [showMoneyManagementModal, setShowMoneyManagementModal] = + React.useState(false) + const [selectedMoneyManagement, setSelectedMoneyManagement] = + React.useState() + + async function runBot(backtest: Backtest, isForWatchOnly: boolean) { + const t = new Toast('Bot is starting') + const client = new BotClient({}, apiUrl) + + const request: StartBotRequest = { + accountName: backtest.accountName, + botName: backtest.ticker + '-' + backtest.timeframe.toString(), + botType: BotType.ScalpingBot, + isForWatchOnly: isForWatchOnly, + moneyManagementName: backtest.moneyManagement?.name, + scenario: backtest.scenario, + ticker: backtest.ticker as Ticker, + timeframe: backtest.timeframe, + } + + await client + .bot_Start(request) + .then((botStatus: string) => { + t.update('info', 'Bot status :' + botStatus) + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + async function runOptimizedBacktest(backtest: Backtest) { + const t = new Toast('Optimized backtest is running') + const client = new BacktestClient({}, apiUrl) + + await client + .backtest_Run( + backtest.accountName, + backtest.botType, + backtest.ticker as Ticker, + backtest.scenario, + backtest.timeframe, + false, + daysBetween(backtest.candles[0].date), + backtest.walletBalances[0].value, + '', + false, + backtest.optimizedMoneyManagement + ) + .then((backtest: Backtest) => { + t.update('success', `${backtest.ticker} Backtest Succeeded`) + setBacktests((arr) => [...arr, backtest]) + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + function saveMoneyManagement(moneyManagement: MoneyManagement) { + setSelectedMoneyManagement(moneyManagement) + setShowMoneyManagementModal(true) + } + + return ( +
    + {list.map((backtest: Backtest, index) => ( +
    +
    +
    + +
    + +
    +
    + { + + } +
    + +
    +

    +
    + +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + {backtest.ticker} + {botStatusResult( + backtest.growthPercentage, + backtest.hodlPercentage + )} +

    +
    +
    + + + + +
    +
    +
    + + {/* */} + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized > 0 ? p : null + })} + > + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized <= 0 ? p : null + })} + > + +
    + +
    +
    + + + + +
    +
    +
    +
    + WR {backtest.winRate?.toFixed(2).toString()} % +
    +
    + PNL {backtest.growthPercentage?.toFixed(2).toString()} % +
    +
    +
    +
    +
    +
    + ))} + + setShowMoneyManagementModal(false)} + /> +
    + ) +} + +export default BacktestCards diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx new file mode 100644 index 0000000..f8c2f75 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx @@ -0,0 +1,361 @@ +import { useQuery } from '@tanstack/react-query' +import React, { useEffect, useState } from 'react' +import { useForm, type SubmitHandler } from 'react-hook-form' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { + Backtest, + MoneyManagement, + Ticker, +} from '../../../generated/ManagingApi' +import { + AccountClient, + BacktestClient, + BotType, + DataClient, + MoneyManagementClient, + ScenarioClient, + Timeframe, +} from '../../../generated/ManagingApi' +import type { + BacktestModalProps, + IBacktestsFormInput, +} from '../../../global/type' +import { Loader, Slider } from '../../atoms' +import { Modal, Toast } from '../../mollecules' +import FormInput from '../../mollecules/FormInput/FormInput' +import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement' + +const BacktestModal: React.FC = ({ + showModal, + closeModal, + setBacktests, + showLoopSlider = false, +}) => { + const [selectedAccount, setSelectedAccount] = React.useState() + const [selectedTimeframe, setSelectedTimeframe] = React.useState() + const [selectedLoopQuantity, setLoopQuantity] = React.useState( + showLoopSlider ? 3 : 1 + ) + const [balance, setBalance] = React.useState(10000) + const [days, setDays] = React.useState(-10) + + const [customMoneyManagement, setCustomMoneyManagement] = + React.useState() + const [selectedMoneyManagement, setSelectedMoneyManagement] = + useState() + const [showCustomMoneyManagement, setShowCustomMoneyManagement] = + useState(false) + + const { apiUrl } = useApiUrlStore() + + const scenarioClient = new ScenarioClient({}, apiUrl) + const accountClient = new AccountClient({}, apiUrl) + const dataClient = new DataClient({}, apiUrl) + const moneyManagementClient = new MoneyManagementClient({}, apiUrl) + const backtestClient = new BacktestClient({}, apiUrl) + + const { register, handleSubmit } = useForm() + const onSubmit: SubmitHandler = async (form) => { + const { scenarioName, tickers } = form + closeModal() + for (let sIndex = 0; sIndex < scenarioName.length; sIndex++) { + for (let tIndex = 0; tIndex < tickers.length; tIndex++) { + await runBacktest( + form, + form.tickers[tIndex], + form.scenarioName[sIndex], + customMoneyManagement, + 1 + ) + } + } + } + + async function runBacktest( + form: IBacktestsFormInput, + ticker: string, + scenarioName: string, + moneyManagement: MoneyManagement | undefined, + loopCount: number + ) { + const t = new Toast(ticker + ' is running') + await backtestClient + .backtest_Run( + form.accountName, + form.botType, + ticker as Ticker, + scenarioName, + form.timeframe, + false, + days, + balance, + selectedMoneyManagement, + form.save, + selectedMoneyManagement ? undefined : moneyManagement + ) + .then((backtest: Backtest) => { + t.update('success', `${backtest.ticker} Backtest Succeeded`) + setBacktests((arr) => [...arr, backtest]) + + if (showLoopSlider && selectedLoopQuantity > loopCount) { + const nextCount = loopCount + 1 + const mm: MoneyManagement = { + balanceAtRisk: backtest.optimizedMoneyManagement.balanceAtRisk, + leverage: backtest.optimizedMoneyManagement.leverage, + name: backtest.optimizedMoneyManagement.name + nextCount, + stopLoss: backtest.optimizedMoneyManagement.stopLoss, + takeProfit: backtest.optimizedMoneyManagement.takeProfit, + timeframe: backtest.optimizedMoneyManagement.timeframe, + } + runBacktest(form, ticker, scenarioName, mm, nextCount) + } + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + function setSelectedAccountEvent(e: React.ChangeEvent) { + setSelectedAccount(e.target.value) + } + + function setSelectedTimeframeEvent(e: any) { + setSelectedTimeframe(e.target.value) + } + + function onMoneyManagementChange(e: any) { + if (e.target.value == 'Custom') { + setShowCustomMoneyManagement(true) + setSelectedMoneyManagement(e.target.value) + } else { + setShowCustomMoneyManagement(false) + setCustomMoneyManagement(undefined) + setSelectedMoneyManagement(undefined) + } + } + + const { data: scenarios } = useQuery({ + queryFn: () => scenarioClient.scenario_GetScenarios(), + queryKey: ['scenarios'], + }) + + const { data: accounts } = useQuery({ + onSuccess: () => { + if (accounts) { + setSelectedAccount(accounts[0].name) + } + setSelectedTimeframe(Timeframe.FiveMinutes) + }, + queryFn: () => accountClient.account_GetAccounts(), + queryKey: ['accounts'], + }) + + const { data: tickers, refetch: refetchTickers } = useQuery({ + enabled: !!selectedAccount && !!selectedTimeframe, + queryFn: () => { + if (selectedAccount && selectedTimeframe) { + return dataClient.data_GetTickers(selectedAccount, selectedTimeframe) + } + }, + queryKey: ['tickers', selectedAccount, selectedTimeframe], + }) + + const { data: moneyManagements } = useQuery({ + enabled: !!selectedTimeframe, + onSuccess: (data) => { + if (data) { + setSelectedMoneyManagement(data[0].name) + } + }, + queryFn: async () => { + if (selectedTimeframe) { + const mm = + await moneyManagementClient.moneyManagement_GetMoneyManagements() + mm.push({ + balanceAtRisk: 1, + leverage: 1, + name: 'Custom', + stopLoss: 1, + takeProfit: 1, + timeframe: selectedTimeframe, + }) + return mm + } + }, + queryKey: ['moneyManagements', selectedTimeframe], + }) + + useEffect(() => { + if (selectedAccount && selectedTimeframe) { + refetchTickers() + } + }, [selectedAccount, selectedTimeframe]) + + if (!accounts || !scenarios || !moneyManagements) { + return + } + + return ( + + + + + + + + + + + + + + + + + + + + setDays(e.target.value)} + step="1" + min="-360" + max="-1" + > + + + + setBalance(e.target.value)} + step="1000" + min="1000" + max="100000" + > + + + + + + + + + + + {/* Loop Quantity */} + {showLoopSlider ? ( + + setLoopQuantity(e.target.value)} + step="1" + min="1" + max="20" + > + + ) : null} + +
    + +
    + +
    + +
    +
    + ) +} +export default BacktestModal diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx new file mode 100644 index 0000000..1ce93dd --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx @@ -0,0 +1,47 @@ +import { TradeChart, CardPositionItem } from '..' +import type { IBotRowDetails } from '../../../global/interface' +import { CardPosition } from '../../mollecules' + +const BacktestRowDetails: React.FC = ({ + candles, + positions, + walletBalances, +}) => { + return ( + <> +
    +
    + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized > 0 ? p : null + })} + > + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized <= 0 ? p : null + })} + > + +
    +
    +
    + +
    +
    +
    + + ) +} + +export default BacktestRowDetails diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx new file mode 100644 index 0000000..4289a33 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx @@ -0,0 +1,275 @@ +import { + ChevronDownIcon, + ChevronRightIcon, + PlayIcon, + TrashIcon, +} from '@heroicons/react/solid' +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { + Backtest, + StartBotRequest, + Ticker, +} from '../../../generated/ManagingApi' +import { BacktestClient, BotClient } from '../../../generated/ManagingApi' +import type { IBacktestCards } from '../../../global/type' +import { Toast, SelectColumnFilter, Table } from '../../mollecules' + +import BacktestRowDetails from './backtestRowDetails' + +const BacktestTable: React.FC = ({ list, isFetching }) => { + const [rows, setRows] = useState([]) + const { apiUrl } = useApiUrlStore() + + async function runBot(backtest: Backtest, isForWatchOnly: boolean) { + const t = new Toast('Bot is starting') + const client = new BotClient({}, apiUrl) + + const request: StartBotRequest = { + accountName: backtest.accountName, + botName: backtest.ticker + '-' + backtest.timeframe.toString(), + botType: backtest.botType, + isForWatchOnly: isForWatchOnly, + moneyManagementName: '', + scenario: backtest.scenario, + ticker: backtest.ticker as Ticker, + timeframe: backtest.timeframe, + } + + await client + .bot_Start(request) + .then((botStatus: string) => { + t.update('info', 'Bot status :' + botStatus) + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + async function deleteBacktest(id: string) { + const t = new Toast('Deleting backtest') + const client = new BacktestClient({}, apiUrl) + + await client + .backtest_DeleteBacktest(id) + .then(() => { + t.update('success', 'Backtest deleted') + }) + .catch((err) => { + t.update('error', err) + }) + } + + const columns = React.useMemo( + () => [ + { + Header: 'Informations', + columns: [ + { + Cell: ({ row }: any) => ( + // Use Cell to render an expander for each row. + // We can use the getToggleRowExpandedProps prop-getter + // to build the expander. + + {row.isExpanded ? ( + + ) : ( + + )} + + ), + + // Make sure it has an ID + Header: ({ + getToggleAllRowsExpandedProps, + isAllRowsExpanded, + }: any) => ( + + {isAllRowsExpanded ? 'v' : '>'} + + ), + // Build our expander column + id: 'expander', + }, + { + Filter: SelectColumnFilter, + Header: 'Ticker', + accessor: 'ticker', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Timeframe', + accessor: 'timeframe', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Scenario', + accessor: 'scenario', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'BotType', + accessor: 'botType', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Account', + accessor: 'accountName', + disableSortBy: true, + }, + ], + }, + { + Header: 'Results', + columns: [ + { + Cell: ({ cell }: any) => ( + <>{cell.row.values.finalPnl.toFixed(2)} $ + ), + Header: 'Pnl $', + accessor: 'finalPnl', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({ cell }: any) => ( + <>{cell.row.values.hodlPercentage.toFixed(2)} % + ), + Header: 'Hodl %', + accessor: 'hodlPercentage', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({ cell }: any) => <>{cell.row.values.winRate} %, + Header: 'Winrate', + accessor: 'winRate', + disableFilters: true, + }, + { + Cell: ({ cell }: any) => ( + <>{cell.row.values.growthPercentage.toFixed(2)} % + ), + Header: 'Pnl %', + accessor: 'growthPercentage', + disableFilters: true, + sortType: 'basic', + }, + { + Cell: ({ cell }: any) => ( + <> + {( + cell.row.values.growthPercentage - + cell.row.values.hodlPercentage + ).toFixed(2)} + + ), + Header: 'H/P', + accessor: 'diff', + disableFilters: true, + sortType: 'basic', + }, + ], + }, + { + Header: 'Action', + columns: [ + { + Cell: ({ cell }: any) => ( + <> +
    + +
    + + ), + Header: '', + accessor: 'id', + disableFilters: true, + }, + // { + // Cell: ({ cell }) => ( + // <> + //
    + // + //
    + // + // ), + // Header: '', + // accessor: 'watcher', + // disableFilters: true, + // }, + { + Cell: ({ cell }: any) => ( + <> +
    + +
    + + ), + Header: '', + accessor: 'bot', + disableFilters: true, + }, + ], + }, + ], + [] + ) + + useEffect(() => { + setRows(list) + }, [list]) + + const renderRowSubComponent = React.useCallback( + ({ row }: any) => ( + <> + + + ), + [] + ) + + return ( +
    + {isFetching ? ( + + ) : ( +
    + )} + + ) +} + +export default BacktestTable diff --git a/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx b/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx new file mode 100644 index 0000000..78f3573 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from 'react' + +import type { MoneyManagement, Timeframe } from '../../../generated/ManagingApi' +import { Slider } from '../../atoms' +import FormInput from '../../mollecules/FormInput/FormInput' + +type ICustomMoneyManagement = { + onCreateMoneyManagement: (moneyManagement: MoneyManagement) => void + timeframe: Timeframe + showCustomMoneyManagement: boolean +} + +const CustomMoneyManagement: React.FC = ({ + onCreateMoneyManagement, + timeframe, + showCustomMoneyManagement, +}) => { + const [balanceAtRisk, setBalanceAtRisk] = useState(1) + const [leverage, setLeverage] = useState(1) + const [takeProfit, setTakeProfit] = useState(1) + const [stopLoss, setStopLoss] = useState(1) + + const handleCreateMoneyManagement = () => { + const moneyManagement: MoneyManagement = { + balanceAtRisk, + leverage, + name: 'custom', + stopLoss, + takeProfit, + timeframe, + } + onCreateMoneyManagement(moneyManagement) + } + + useEffect(() => { + handleCreateMoneyManagement() + }, [balanceAtRisk, leverage, takeProfit, stopLoss]) + + return ( + <> + {showCustomMoneyManagement ? ( +
    + +
    + Custom MoneyManagement +
    +
    + + setBalanceAtRisk(parseInt(e.target.value))} + min="1" + max="100" + step="1" + suffixValue=" %" + > + + + + setLeverage(e.target.value)} + prefixValue="x " + > + + + + setTakeProfit(e.target.value)} + step="0.01" + max="20" + suffixValue=" %" + > + + + + setStopLoss(e.target.value)} + step="0.01" + max="20" + suffixValue=" %" + > + +
    +
    + ) : null} + + ) +} + +export default CustomMoneyManagement diff --git a/src/Managing.WebApp/src/components/organism/Positions/PositionList.tsx b/src/Managing.WebApp/src/components/organism/Positions/PositionList.tsx new file mode 100644 index 0000000..af7231d --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Positions/PositionList.tsx @@ -0,0 +1,128 @@ +import { StopIcon } from '@heroicons/react/solid' +import moment from 'moment' +import React from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import { + TradeDirection, + type Position, + Ticker, + PositionStatus, + TradingClient, +} from '../../../generated/ManagingApi' +import { Toast, Table } from '../../mollecules' + +import PositionStatusBadge from './PositionStatusBadge' + +type IPositionList = { + positions: Position[] + isFetching: boolean +} + +const PositionsList: React.FC = ({ positions, isFetching }) => { + const { apiUrl } = useApiUrlStore() + + async function closePosition(identifier: string) { + const t = new Toast('Closing position') + const client = new TradingClient({}, apiUrl) + await client + .trading_ClosePosition(identifier) + .then(() => { + t.update('success', 'Position closed') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + const columns = React.useMemo( + () => [ + { + Cell: ({ cell }: any) => ( + <> +
    + +
    + + ), + Header: 'Status', + accessor: 'status', + disableFilters: true, + sortType: 'basic', + }, + + { + Cell: ({ cell }: any) =>
    {Object.values(Ticker)[cell.value]}
    , + Header: 'Ticker', + accessor: 'ticker', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( +
    {Object.values(TradeDirection)[cell.value]}
    + ), + Header: 'Direction', + accessor: 'originDirection', + disableFilters: true, + disableSortBy: true, + }, + { + Header: 'Account', + accessor: 'accountName', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => moment(cell.value).fromNow(), + Header: 'Date', + accessor: 'date', + disableFilters: true, + }, + { + Cell: ({ cell }: any) => ( +
    {(cell.value.realized as number).toFixed(4)} $
    + ), + Header: 'uPNL', + accessor: 'profitAndLoss', + disableFilters: true, + }, + { + Cell: ({ cell }) => ( + <> +
    + +
    + + ), + Header: 'Actions', + accessor: 'identifier', + disableFilters: true, + }, + ], + [] + ) + return ( +
    + {isFetching ? ( +
    + +
    + ) : ( +
    + )} + + ) +} + +export default PositionsList diff --git a/src/Managing.WebApp/src/components/organism/Positions/PositionStatusBadge.tsx b/src/Managing.WebApp/src/components/organism/Positions/PositionStatusBadge.tsx new file mode 100644 index 0000000..2ac4f64 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Positions/PositionStatusBadge.tsx @@ -0,0 +1,30 @@ +import { PositionStatus } from '../../../generated/ManagingApi' + +type IPositionStatusBadge = { + status: PositionStatus +} +function statusClasses(status: PositionStatus) { + let commonClasses = 'badge badge-xs ' + switch (status) { + case PositionStatus.Canceled: + case PositionStatus.Rejected: + commonClasses += 'bg-red-100' + break + case PositionStatus.New: + commonClasses += 'bg-blue-100' + break + case PositionStatus.PartiallyFilled: + commonClasses += 'bg-orange-100' + break + case PositionStatus.Filled: + commonClasses += 'bg-green-100' + break + default: + break + } + return commonClasses +} + +export default function PositionStatusBadge({ status }: IPositionStatusBadge) { + return +} diff --git a/src/Managing.WebApp/src/components/organism/SpotLightBadge/SpotLightBadge.tsx b/src/Managing.WebApp/src/components/organism/SpotLightBadge/SpotLightBadge.tsx new file mode 100644 index 0000000..c1e260a --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/SpotLightBadge/SpotLightBadge.tsx @@ -0,0 +1,31 @@ +import moment from 'moment' + +import { TradeDirection } from '../../../generated/ManagingApi' +import type { ISpotlightBadge } from '../../../global/type' + +function GetBadgeColor(direction: TradeDirection | undefined) { + switch (direction) { + case TradeDirection.Long: + return 'badge bg-success' + case TradeDirection.Short: + return 'badge bg-error' + case TradeDirection.None: + return 'badge bg-warning' + default: + return 'badge' + } +} + +export default function SpotLightBadge({ + direction, + date, + price, +}: ISpotlightBadge) { + const tooltipText = + date == undefined ? 'No signal' : moment(date).fromNow() + ' @ ' + price + return ( +
    +
    +
    + ) +} diff --git a/src/Managing.WebApp/src/components/organism/StatusBadge/StatusBadge.tsx b/src/Managing.WebApp/src/components/organism/StatusBadge/StatusBadge.tsx new file mode 100644 index 0000000..4c44184 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/StatusBadge/StatusBadge.tsx @@ -0,0 +1,14 @@ +import type { IStatusBadge } from '../../../global/type' + +function statusClasses(status: string, isForWatchOnly: boolean) { + const commonClasses = 'badge badge-xs' + if (isForWatchOnly) { + return `${commonClasses} 'bg-blue-500'` + } + + return `${commonClasses} ${status == 'Up' ? 'bg-green-500' : 'bg-red-500'}` +} + +export default function StatusBadge({ status, isForWatchOnly }: IStatusBadge) { + return +} diff --git a/src/Managing.WebApp/src/components/organism/Trading/CardPositionItem.tsx b/src/Managing.WebApp/src/components/organism/Trading/CardPositionItem.tsx new file mode 100644 index 0000000..8a8e192 --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Trading/CardPositionItem.tsx @@ -0,0 +1,34 @@ +import { PositionStatus } from '../../../generated/ManagingApi' +import type { ICardPositionFlipped } from '../../../global/type' +import { CardText } from '../../mollecules' + +const CardPositionFlipped: React.FC = ({ positions }) => { + return ( + <> + { + const realized = p.profitAndLoss?.realized ?? 0 + return realized > 0 && p.status == PositionStatus.Flipped + ? p + : null + }) + .length.toString() + + ' | ' + + positions + .filter((p) => { + const realized = p.profitAndLoss?.realized ?? 0 + return realized <= 0 && p.status == PositionStatus.Flipped + ? p + : null + }) + .length.toString() + } + > + + ) +} + +export default CardPositionFlipped diff --git a/src/Managing.WebApp/src/components/organism/Trading/Summary.tsx b/src/Managing.WebApp/src/components/organism/Trading/Summary.tsx new file mode 100644 index 0000000..021531d --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Trading/Summary.tsx @@ -0,0 +1,133 @@ +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' +import React, { useEffect, useState } from 'react' + +import type { TradingBot } from '../../../generated/ManagingApi' +import { PositionStatus, TradeDirection } from '../../../generated/ManagingApi' +import type { IAccountBalanceProps } from '../../../global/type' + +function GetGlobalWinrate(bots: TradingBot[]) { + if (bots == null || bots == undefined || bots.length == 0) { + return 0 + } + + let totalPositions = 0 + let winningPosition = 0 + + bots.forEach((bot) => { + totalPositions += bot.positions.filter( + (p) => p.status != PositionStatus.New + ).length + winningPosition += bot.positions.filter((p) => { + const realized = p.profitAndLoss?.realized ?? 0 + return realized > 0 && + (p.status == PositionStatus.Finished || + p.status == PositionStatus.Flipped) + ? p + : null + }).length + }) + + if (totalPositions == 0) return 0 + + return (winningPosition * 100) / totalPositions +} + +function GetPositionCount( + bots: TradingBot[], + direction: TradeDirection, + status: PositionStatus +) { + let totalPositions = 0 + + if (bots == null || bots == undefined) { + return 0 + } + + bots.forEach((bot) => { + totalPositions += bot.positions.filter( + (p) => p.status == status && p.originDirection == direction + ).length + }) + + return totalPositions +} + +const Summary: React.FC = ({ bots }) => { + const [globalPnl, setGlobalPnl] = useState(0) + const [globalWinrate, setGlobalWinrate] = useState(0) + + const [openLong, setLong] = useState(0) + const [openShort, setShort] = useState(0) + + const [closedLong, setClosedLong] = useState(0) + const [closedShort, setClosedShort] = useState(0) + + useEffect(() => { + if (bots) { + const pnl = bots.reduce((acc, bot) => { + return acc + bot.profitAndLoss + }, 0) + setGlobalPnl(pnl) + setGlobalWinrate(GetGlobalWinrate(bots)) + setLong( + GetPositionCount(bots, TradeDirection.Long, PositionStatus.Filled) + ) + setShort( + GetPositionCount(bots, TradeDirection.Short, PositionStatus.Filled) + ) + setClosedLong( + GetPositionCount(bots, TradeDirection.Long, PositionStatus.Finished) + + GetPositionCount(bots, TradeDirection.Long, PositionStatus.Flipped) + ) + setClosedShort( + GetPositionCount(bots, TradeDirection.Short, PositionStatus.Finished) + + GetPositionCount(bots, TradeDirection.Short, PositionStatus.Flipped) + ) + } + }, [bots]) + + return ( +
    +
    + +
    +
    +
    Bots running
    +
    {bots.length}
    +
    + +
    +
    Total Profit
    +
    {globalPnl.toFixed(4)} $
    +
    + +
    +
    Global Winrate
    +
    + {globalWinrate ? globalWinrate.toFixed(2) : 0} % +
    +
    + +
    +
    Positions Openend
    +
    + {openLong} {' '} + {openShort}{' '} + {' '} +
    +
    +
    +
    Positions Closed
    +
    + {closedLong}{' '} + {' '} + {closedShort}{' '} + {' '} +
    +
    +
    +
    + ) +} + +export default Summary diff --git a/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx new file mode 100644 index 0000000..60d4daa --- /dev/null +++ b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx @@ -0,0 +1,302 @@ +import type { + CandlestickData, + IChartApi, + ISeriesApi, + PriceLineOptions, + SeriesMarker, + SeriesMarkerShape, + Time, + UTCTimestamp, +} from 'lightweight-charts' +import { LineStyle, createChart, CrosshairMode } from 'lightweight-charts' +import moment from 'moment' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' + +import type { + Candle, + KeyValuePairOfDateTimeAndDecimal, + Position, + Signal, +} from '../../../../generated/ManagingApi' +import { + PositionStatus, + TradeDirection, +} from '../../../../generated/ManagingApi' +import useTheme from '../../../../hooks/useTheme' + +type ITradeChartProps = { + candles: Candle[] + positions: Position[] + signals: Signal[] + walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null + stream?: Candle | null + width: number + height: number +} + +const TradeChart = ({ + candles, + positions, + signals, + walletBalances, + stream, + width, + height, +}: ITradeChartProps) => { + const chartRef = React.useRef(null) + const chart = useRef() + const { themeProperty } = useTheme() + const theme = themeProperty() + const series1 = useRef>() + const [timeDiff, setTimeDiff] = useState(0) + const [candleCount, setCandleCount] = useState(candles.length) + + function buildLine( + color: string, + price: number, + title: string + ): PriceLineOptions { + return { + axisLabelVisible: true, + color: color, + lineStyle: LineStyle.Dotted, + lineVisible: true, + lineWidth: 1, + price: price, + title: title, + } + } + + function buildMarker( + shape: SeriesMarkerShape, + color: string, + direction: TradeDirection, + date: Date, + text?: string + ): SeriesMarker
    + + ) +} + +export default ScenarioTable diff --git a/src/Managing.WebApp/src/pages/scenarioPage/strategyList.tsx b/src/Managing.WebApp/src/pages/scenarioPage/strategyList.tsx new file mode 100644 index 0000000..e75e916 --- /dev/null +++ b/src/Managing.WebApp/src/pages/scenarioPage/strategyList.tsx @@ -0,0 +1,401 @@ +import React, { useEffect, useState } from 'react' +import type { SubmitHandler } from 'react-hook-form' +import { useForm } from 'react-hook-form' +import 'react-toastify/dist/ReactToastify.css' + +import useApiUrlStore from '../../app/store/apiStore' +import { Toast } from '../../components/mollecules' +import type { Strategy } from '../../generated/ManagingApi' +import { + StrategyType, + ScenarioClient, + Timeframe, +} from '../../generated/ManagingApi' + +import StrategyTable from './strategyTable' + +interface IStrategyFormInput { + type: StrategyType + timeframe: Timeframe + name: string + period: number + fastPeriods: number + slowPeriods: number + signalPeriods: number + multiplier: number + stochPeriods: number + smoothPeriods: number + cyclePeriods: number +} + +const StrategyList: React.FC = () => { + const [strategyType, setStrategyType] = useState( + StrategyType.RsiDivergence + ) + const [strategies, setStrategies] = useState([]) + const [showModal, setShowModal] = useState(false) + const { register, handleSubmit } = useForm() + const { apiUrl } = useApiUrlStore() + const scenarioClient = new ScenarioClient({}, apiUrl) + + async function createStrategy(form: IStrategyFormInput) { + const t = new Toast('Creating strategy') + await scenarioClient + .scenario_CreateStrategy( + form.type, + form.timeframe, + form.name, + form.period, + form.fastPeriods, + form.slowPeriods, + form.signalPeriods, + form.multiplier, + form.stochPeriods, + form.smoothPeriods, + form.cyclePeriods + ) + .then((strategy: Strategy) => { + t.update('success', 'Strategy created') + setStrategies((arr) => [...arr, strategy]) + }) + .catch((err) => { + t.update('error', err) + }) + } + + function setStrategyTypeEvent(e: any) { + setStrategyType(e.target.value) + } + + const onSubmit: SubmitHandler = async (form) => { + closeModal() + await createStrategy(form) + } + + useEffect(() => { + scenarioClient.scenario_GetStrategies().then((data) => { + setStrategies(data) + }) + }, []) + + function openModal() { + setShowModal(true) + } + + function closeModal() { + setShowModal(false) + } + + return ( +
    +
    + + + {showModal ? ( + <> +
    +
    +
    +
    + +
    + Strategy builder +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + {strategyType == StrategyType.EmaTrend || + strategyType == StrategyType.RsiDivergence || + strategyType == StrategyType.StDev || + strategyType == StrategyType.EmaCross || + strategyType == StrategyType.RsiDivergenceConfirm ? ( + <> +
    +
    + + +
    +
    + + ) : null} + + {strategyType == StrategyType.MacdCross ? ( + <> +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + ) : null} + + {strategyType == StrategyType.Stc ? ( + <> +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + ) : null} + + {strategyType == StrategyType.SuperTrend || + strategyType == StrategyType.ChandelierExit ? ( + <> +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + ) : null} + + {strategyType == StrategyType.StochRsiTrend ? ( + <> +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + ) : null} +
    + +
    +
    +
    + +
    + + ) : null} +
    +
    + ) +} + +export default StrategyList diff --git a/src/Managing.WebApp/src/pages/scenarioPage/strategyTable.tsx b/src/Managing.WebApp/src/pages/scenarioPage/strategyTable.tsx new file mode 100644 index 0000000..6743d86 --- /dev/null +++ b/src/Managing.WebApp/src/pages/scenarioPage/strategyTable.tsx @@ -0,0 +1,94 @@ +import { TrashIcon } from '@heroicons/react/solid' +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../app/store/apiStore' +import { SelectColumnFilter, Table, Toast } from '../../components/mollecules' +import type { Strategy } from '../../generated/ManagingApi' +import { ScenarioClient } from '../../generated/ManagingApi' + +interface IStrategyList { + list: Strategy[] +} + +const StrategyTable: React.FC = ({ list }) => { + const [rows, setRows] = useState([]) + const { apiUrl } = useApiUrlStore() + + async function deleteBacktest(id: string) { + const t = new Toast('Deleting strategy') + const client = new ScenarioClient({}, apiUrl) + + await client + .scenario_DeleteStrategy(id) + .then(() => { + t.update('info', 'Strategy deleted') + }) + .catch((err) => { + t.update('error', err) + }) + } + + const columns = React.useMemo( + () => [ + { + Header: 'Name', + accessor: 'name', + disableFilters: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Type', + accessor: 'type', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Timeframe', + accessor: 'timeframe', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Signal', + accessor: 'signalType', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Period', + accessor: 'period', + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <> +
    + +
    + + ), + Header: '', + accessor: 'id', + disableFilters: true, + }, + ], + [] + ) + + useEffect(() => { + setRows(list) + }, [list]) + + return ( +
    +
    + + ) +} + +export default StrategyTable diff --git a/src/Managing.WebApp/src/pages/settingsPage/account/accountModal.tsx b/src/Managing.WebApp/src/pages/settingsPage/account/accountModal.tsx new file mode 100644 index 0000000..84681f0 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/account/accountModal.tsx @@ -0,0 +1,178 @@ +import { useState } from 'react' +import type { SubmitHandler } from 'react-hook-form' +import { useForm } from 'react-hook-form' + +import useApiUrlStore from '../../../app/store/apiStore' +import { Modal, Toast } from '../../../components/mollecules' +import type { Account } from '../../../generated/ManagingApi' +import { + AccountType, + AccountClient, + TradingExchanges, +} from '../../../generated/ManagingApi' +import type { IAccountFormInput, IModalProps } from '../../../global/type' + +const AccountModal: React.FC = ({ showModal, toggleModal }) => { + const [selectedExchange, setSelectedExchange] = useState() + const [selectedType, setSelectedType] = useState() + const { register, handleSubmit } = useForm() + const { apiUrl } = useApiUrlStore() + + async function createMoneyManagement(form: IAccountFormInput) { + const t = new Toast('Creating account') + const client = new AccountClient({}, apiUrl) + const a: Account = { + exchange: form.exchange, + key: form.key, + name: form.name, + secret: form.secret, + type: form.type, + } + await client + .account_PostAccount(a) + .then(() => { + t.update('success', 'Account created') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + const onSubmit: SubmitHandler = async (form) => { + // @ts-ignore + toggleModal() + await createMoneyManagement(form) + } + + function setSelectedExchangeEvent(e: any) { + setSelectedExchange(e.target.value) + } + + function setSelectedTypeEvent(e: any) { + setSelectedType(e.target.value) + } + + return ( +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + {selectedExchange != TradingExchanges.Evm && + selectedType != AccountType.Trader ? ( + <> +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + ) : null} + + {selectedExchange == TradingExchanges.Evm && + selectedType == AccountType.Watch ? ( + <> +
    +
    + + +
    +
    + + ) : null} + +
    + +
    +
    +
    + ) +} + +export default AccountModal diff --git a/src/Managing.WebApp/src/pages/settingsPage/account/accountRowDetails.tsx b/src/Managing.WebApp/src/pages/settingsPage/account/accountRowDetails.tsx new file mode 100644 index 0000000..47d8415 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/account/accountRowDetails.tsx @@ -0,0 +1,63 @@ +import React from 'react' + +import { SelectColumnFilter, Table } from '../../../components/mollecules' +import { IAccountRowDetail } from '../../../global/type' + +const columns = [ + { + Header: 'Chain', + accessor: 'chain.name', + disableFilters: true, + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Assets', + accessor: 'tokenName', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <> +
    + {cell.row.values.amount.toFixed(4)} +
    + + ), + Header: 'Quantity', + accessor: 'amount', + disableFilters: true, + }, + { + Cell: ({ cell }: any) => <>{cell.row.values.value.toFixed(2)} $, + Header: 'USD', + accessor: 'value', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => <> {cell.row.values.price} $, + Header: 'Price', + accessor: 'price', + disableFilters: true, + }, +] + +const AccountRowDetails: React.FC = ({ + balances, + showTotal, +}) => { + return ( + <> +
    + + ) +} + +export default AccountRowDetails diff --git a/src/Managing.WebApp/src/pages/settingsPage/account/accountSettings.tsx b/src/Managing.WebApp/src/pages/settingsPage/account/accountSettings.tsx new file mode 100644 index 0000000..708dde6 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/account/accountSettings.tsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { Account } from '../../../generated/ManagingApi' +import { AccountClient } from '../../../generated/ManagingApi' + +import AccountModal from './accountModal' +import AccountTable from './accountTable' + +const AccountSettings: React.FC = () => { + const [accounts, setAccounts] = useState([]) + const [showModal, setShowModal] = useState(false) + const { apiUrl } = useApiUrlStore() + const [isFetching, setIsFetching] = useState(false) + + useEffect(() => { + const client = new AccountClient({}, apiUrl) + setIsFetching(true) + client + .account_GetAccountsBalances() + .then((data) => { + setAccounts(data) + }) + .finally(() => setIsFetching(false)) + }, []) + + function toggleModal() { + setShowModal(!showModal) + } + + function openModal() { + setShowModal(true) + } + + return ( +
    +
    + + + +
    +
    + ) +} +export default AccountSettings diff --git a/src/Managing.WebApp/src/pages/settingsPage/account/accountTable.tsx b/src/Managing.WebApp/src/pages/settingsPage/account/accountTable.tsx new file mode 100644 index 0000000..5574e3c --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/account/accountTable.tsx @@ -0,0 +1,163 @@ +import { + ChevronDownIcon, + ChevronRightIcon, + ClipboardCopyIcon, + TrashIcon, +} from '@heroicons/react/solid' +import React, { useEffect, useState, useMemo } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import { + SelectColumnFilter, + Table, + Toast, +} from '../../../components/mollecules' +import type { Account } from '../../../generated/ManagingApi' +import { AccountClient } from '../../../generated/ManagingApi' + +import AccountRowDetails from './accountRowDetails' + +interface IAccountList { + list: Account[] + isFetching: boolean +} + +const AccountTable: React.FC = ({ list, isFetching }) => { + const [rows, setRows] = useState([]) + const { apiUrl } = useApiUrlStore() + + async function deleteAcount(accountName: string) { + const t = new Toast('Deleting money management') + const client = new AccountClient({}, apiUrl) + + await client + .account_DeleteAccount(accountName) + .then(() => { + t.update('success', 'Account deleted') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + const columns = useMemo( + () => [ + { + Cell: ({ row }: any) => ( + + {row.isExpanded ? ( + + ) : ( + + )} + + ), + + // Make sure it has an ID + Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }: any) => ( + + {isAllRowsExpanded ? 'v' : '>'} + + ), + // Build our expander column + id: 'expander', + }, + { + Header: 'Name', + accessor: 'name', + disableFilters: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Exchange', + accessor: 'exchange', + disableSortBy: true, + }, + { + Filter: SelectColumnFilter, + Header: 'Type', + accessor: 'type', + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <> +
    + {cell.row.values.key.substring(0, 6)}... + {cell.row.values.key.slice(-4)} +
    + + ), + Header: 'Key', + accessor: 'key', + disableFilters: true, + }, + { + Cell: ({ cell }: any) => ( + <> +
    + +
    +
    + +
    + + ), + Header: 'Actions', + accessor: 'id', + disableFilters: true, + }, + ], + [] + ) + + useEffect(() => { + setRows(list) + }, [list]) + + const renderRowSubComponent = React.useCallback( + ({ row }: any) => ( + <> + {row.original.balances != undefined ? ( + + ) : ( +
    No balances
    + )} + + ), + [] + ) + + return ( + <> + {isFetching ? ( +
    + +
    + ) : ( +
    + )} + + ) +} + +export default AccountTable diff --git a/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagement.tsx b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagement.tsx new file mode 100644 index 0000000..7e5bbda --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagement.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { MoneyManagement } from '../../../generated/ManagingApi' +import { MoneyManagementClient } from '../../../generated/ManagingApi' + +import MoneyManagementModal from './moneyManagementModal' +import MoneyManagementTable from './moneymanagementTable' + +const MoneyManagementSettings: React.FC = () => { + const [moneyManagements, setMoneyManagements] = useState( + [] + ) + const [showModal, setShowModal] = useState(false) + const { apiUrl } = useApiUrlStore() + + useEffect(() => { + const client = new MoneyManagementClient({}, apiUrl) + client.moneyManagement_GetMoneyManagements().then((data) => { + setMoneyManagements(data) + }) + }, []) + + function toggleModal() { + setShowModal(!showModal) + } + + function openModal() { + setShowModal(true) + } + + function closeModal() { + setShowModal(false) + } + + return ( +
    +
    + + + +
    +
    + ) +} +export default MoneyManagementSettings diff --git a/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagementModal.tsx b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagementModal.tsx new file mode 100644 index 0000000..79043dd --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneyManagementModal.tsx @@ -0,0 +1,190 @@ +import { useEffect, useState } from 'react' +import type { SubmitHandler } from 'react-hook-form' +import { useForm } from 'react-hook-form' + +import useApiUrlStore from '../../../app/store/apiStore' +import { Slider } from '../../../components/atoms' +import { FormInput, Modal, Toast } from '../../../components/mollecules' +import type { MoneyManagement } from '../../../generated/ManagingApi' +import { + MoneyManagementClient, + Timeframe, +} from '../../../generated/ManagingApi' +import type { + IMoneyManagementModalProps, + IMoneyManagementFormInput, +} from '../../../global/type' + +const MoneyManagementModal: React.FC = ({ + showModal, + onClose, + moneyManagement, + disableInputs = false, +}) => { + const [balanceAtRisk, setBalanceAtRisk] = useState(5) + const [takeProfit, setTakeProfit] = useState(20) + const [name, setName] = useState('') + const [stopLoss, setStopLoss] = useState(10) + const [leverage, setLeverage] = useState(1) + const [timeframe, setTimeframe] = useState( + Timeframe.FifteenMinutes + ) + const { reset, register, handleSubmit } = useForm() + const { apiUrl } = useApiUrlStore() + + async function createMoneyManagement(form: IMoneyManagementFormInput) { + const t = new Toast('Creating settings') + const client = new MoneyManagementClient({}, apiUrl) + const mm: MoneyManagement = { + balanceAtRisk: balanceAtRisk / 100, + leverage: leverage, + name: name, + stopLoss: stopLoss / 100, + takeProfit: takeProfit / 100, + timeframe: form.timeframe, + } + + await client + .moneyManagement_PostMoneyManagement(mm) + .then(() => { + t.update('success', 'Settings created') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + const onSubmit: SubmitHandler = async (form) => { + // @ts-ignore + await createMoneyManagement(form) + onClose() + } + + function onChangeName(e: any) { + setName(e.target.value) + } + + function onTakeProfitChange(e: any) { + setTakeProfit(e.target.value) + } + + function onStopLossChange(e: any) { + setStopLoss(e.target.value) + } + + function onLeverageChange(e: any) { + setLeverage(e.target.value) + } + + useEffect(() => { + if (moneyManagement) { + setBalanceAtRisk(moneyManagement.balanceAtRisk * 100) + setTakeProfit(moneyManagement.takeProfit * 100) + setStopLoss(moneyManagement.stopLoss * 100) + setLeverage(moneyManagement.leverage) + setTimeframe(moneyManagement.timeframe) + setName(moneyManagement.name) + + const defaultValues: MoneyManagement = { + balanceAtRisk: moneyManagement.balanceAtRisk, + leverage: moneyManagement.leverage, + name: moneyManagement.name || '', + stopLoss: moneyManagement.stopLoss, + takeProfit: moneyManagement.takeProfit, + timeframe: moneyManagement.timeframe, + } + reset({ ...defaultValues }) + } + }, [showModal, moneyManagement]) + + return ( + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    {(takeProfit / stopLoss).toFixed(2)}
    +
    +
    + {disableInputs ? null : ( +
    + +
    + )} +
    + ) +} + +export default MoneyManagementModal diff --git a/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx new file mode 100644 index 0000000..7cfea9e --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx @@ -0,0 +1,133 @@ +import { PencilAltIcon, TrashIcon } from '@heroicons/react/solid' +import React, { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import { + Toast, + SelectColumnFilter, + Table, +} from '../../../components/mollecules' +import type { MoneyManagement } from '../../../generated/ManagingApi' +import { MoneyManagementClient } from '../../../generated/ManagingApi' +import type { IMoneyManagementList } from '../../../global/type' + +import MoneyManagementModal from './moneyManagementModal' + +const MoneyManagementTable: React.FC = ({ list }) => { + const [rows, setRows] = useState([]) + const [showModal, setShowModal] = useState(false) + const [selectedRow, setSelectedRow] = useState() + const { apiUrl } = useApiUrlStore() + + async function deleteMoneyManagement(name: string) { + const t = new Toast('Deleting money management') + const client = new MoneyManagementClient({}, apiUrl) + + await client + .moneyManagement_DeleteMoneyManagement(name) + .then(() => { + t.update('success', 'Configuration deleted') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) + } + + function toggleModal() { + setShowModal(!showModal) + } + + function openModal(mm: MoneyManagement) { + setSelectedRow(mm) + setShowModal(true) + } + + const columns = React.useMemo( + () => [ + { + Filter: SelectColumnFilter, + Header: 'Timeframe', + accessor: 'timeframe', + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => <>x{cell.row.values.leverage}, + Header: 'Leverage', + accessor: 'leverage', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => <>{cell.row.values.balanceAtRisk * 100} %, + Header: 'Balance used', + accessor: 'balanceAtRisk', + disableFilters: true, + }, + { + Cell: ({ cell }) => <>{cell.row.values.stopLoss * 100} %, + Header: 'SL', + accessor: 'stopLoss', + disableFilters: true, + }, + { + Cell: ({ cell }) => <>{cell.row.values.takeProfit * 100} %, + Header: 'TP', + accessor: 'takeProfit', + disableFilters: true, + }, + { + Cell: ({ cell }) => ( + <> + {(cell.row.values.takeProfit / cell.row.values.stopLoss).toFixed(2)} + + ), + Header: 'R/R', + accessor: 'riskReward', + disableFilters: true, + }, + { + Cell: ({ cell }) => ( + <> +
    + +
    +
    + +
    + + ), + Header: 'Actions', + accessor: 'id', + disableFilters: true, + }, + ], + [] + ) + + useEffect(() => { + setRows(list) + }, [list]) + + return ( +
    +
    + + + ) +} + +export default MoneyManagementTable diff --git a/src/Managing.WebApp/src/pages/settingsPage/settings.tsx b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx new file mode 100644 index 0000000..404d069 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react' + +import { Tabs } from '../../components/mollecules' + +import AccountSettings from './account/accountSettings' +import MoneyManagementSettings from './moneymanagement/moneyManagement' +import Theme from './theme' + +type TabsType = { + label: string + index: number + Component: React.FC<{}> +}[] + +// Tabs Array +const tabs: TabsType = [ + { + Component: MoneyManagementSettings, + index: 1, + label: 'Money Management', + }, + { + Component: AccountSettings, + index: 2, + label: 'Account Settings', + }, + { + Component: Theme, + index: 3, + label: 'Theme', + }, +] + +const Settings: React.FC = () => { + const [selectedTab, setSelectedTab] = useState(tabs[0].index) + + return ( +
    +
    + +
    +
    + ) +} + +export default Settings diff --git a/src/Managing.WebApp/src/pages/settingsPage/theme.tsx b/src/Managing.WebApp/src/pages/settingsPage/theme.tsx new file mode 100644 index 0000000..a4b4515 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/theme.tsx @@ -0,0 +1,14 @@ +import { ThemeSelector } from '../../components/mollecules' + +const Theme: React.FC = () => { + return ( +
    +

    Settings

    +

    +

    Theme

    + +
    + ) +} + +export default Theme diff --git a/src/Managing.WebApp/src/pages/toolsPage/rektFees.tsx b/src/Managing.WebApp/src/pages/toolsPage/rektFees.tsx new file mode 100644 index 0000000..24f8f3c --- /dev/null +++ b/src/Managing.WebApp/src/pages/toolsPage/rektFees.tsx @@ -0,0 +1,158 @@ +import { useEffect, useState } from 'react' +import type { SubmitHandler } from 'react-hook-form' +import { useForm } from 'react-hook-form' +import Plot from 'react-plotly.js' + +import useTheme from '../../hooks/useTheme' + +interface IRektToolInput { + fundingFeeRate: number + feePerTx: number + averageTradeTime: number +} + +const RektFees = () => { + const { register, handleSubmit } = useForm() + const [amountPerPosition, setAmountPerPosition] = useState([]) + const [z, setZ] = useState([]) + const [fundingFeesRate, setFundingFeesRate] = useState(0.0051) + const [feePerTx, setFeePerTx] = useState(0.13) + const [averageTradeTime, setAverageTradeTime] = useState(960) + const { themeProperty } = useTheme() + const theme = themeProperty() + const onSubmit: SubmitHandler = async (form) => { + setFundingFeesRate(form.fundingFeeRate) + setFeePerTx(form.feePerTx) + setAverageTradeTime(form.averageTradeTime) + } + + useEffect(() => { + updateAmountPerPosition() + updateAxis() + }, [fundingFeesRate, feePerTx, averageTradeTime]) + + function updateAmountPerPosition() { + const array: number[] = [] + for (let i = 300; i < 3500; i += 10) { + array.push(i) + } + setAmountPerPosition(array) + } + + function updateAxis() { + const tempZ: number[][] = [] + for (let i = 0; i < amountPerPosition.length; i++) { + const feePerPosition = getFeesForPosition(amountPerPosition[i]) + const zAxis: number[] = [] + + for (let t = 1; t < 1000; t++) { + const totalFees = feePerPosition * t + zAxis.push((totalFees * 100) / amountPerPosition[i]) + } + + tempZ.push(zAxis) + } + setZ(tempZ) + } + + function getFeesForPosition(amountPerPosition: number): number { + const tradeTime = averageTradeTime / 60 + const fundingFees = amountPerPosition * tradeTime * (fundingFeesRate / 100) + const positionFees = amountPerPosition * 0.0001 + const txFees = feePerTx * 2 // Open + SL or TP + + return fundingFees + txFees + positionFees + } + + return ( +
    +
    + + + + + + + +
    + ) +} +export default RektFees diff --git a/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlight.tsx b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlight.tsx new file mode 100644 index 0000000..027928a --- /dev/null +++ b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlight.tsx @@ -0,0 +1,76 @@ +import moment from 'moment' +import { useEffect, useState } from 'react' + +import useApiUrlStore from '../../../app/store/apiStore' +import type { + Spotlight, + SpotlightOverview, +} from '../../../generated/ManagingApi' +import { DataClient } from '../../../generated/ManagingApi' + +import SpotlightSummary from './spotlightSummary' +import SpotlightTable from './spotlightTable' + +interface ISpotlightOverview { + overview: SpotlightOverview | undefined +} +const Overview: React.FC = ({ overview }) => { + return ( + <> +
    Last update : {moment(overview?.dateTime).fromNow()}
    +
    + +
    +
    + {overview?.spotlights.map((s) => ( + + ))} +
    + + ) +} + +interface ISpotlight { + spotlight: Spotlight +} +const SpotlightDetail: React.FC = ({ spotlight }) => { + return ( + <> +
    + +
    + + ) +} + +const SpotlightView = () => { + const [overview, setOverview] = useState() + const [isFetching, setIsFetching] = useState(true) + + const { apiUrl } = useApiUrlStore() + const dataClient = new DataClient({}, apiUrl) + + useEffect(() => { + dataClient + .data_GetSpotlight() + .then((data) => { + setOverview(data) + }) + .finally(() => { + setIsFetching(false) + }) + }, []) + + return ( + <> +
    + {isFetching ? ( + + ) : ( + + )} +
    + + ) +} +export default SpotlightView diff --git a/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightSummary.tsx b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightSummary.tsx new file mode 100644 index 0000000..68557e9 --- /dev/null +++ b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightSummary.tsx @@ -0,0 +1,210 @@ +import React from 'react' + +import { PieChart } from '../../../components/mollecules' +import type { Signal, Spotlight } from '../../../generated/ManagingApi' +import { TradeDirection } from '../../../generated/ManagingApi' +import useTheme from '../../../hooks/useTheme' + +interface ISpotlightSummary { + spotlights: Spotlight[] | undefined +} + +interface ISummaryChart { + fiveMinutes: ISummaryChartItem + fifteenMinutes: ISummaryChartItem + oneHour: ISummaryChartItem + fourHours: ISummaryChartItem + oneDay: ISummaryChartItem +} + +interface ISummaryChartItem { + long: number + short: number + none: number +} + +const SpotlightSummary: React.FC = ({ spotlights }) => { + const { themeProperty } = useTheme() + const theme = themeProperty() + const labels = ['Long', 'Short', 'None'] + const colors = [theme.success, theme.error, 'grey'] + + function GetSummaryChartItem(signal: Signal) { + const summary: ISummaryChartItem = { + long: 0, + none: 0, + short: 0, + } + if (signal) { + switch (signal.direction) { + case TradeDirection.Long: + summary.long++ + break + case TradeDirection.Short: + summary.short++ + break + case TradeDirection.None: + summary.none++ + break + default: + summary.none++ + break + } + } else { + summary.none++ + } + return summary + } + + function GetSignalChart(): React.ReactNode { + const summary: ISummaryChart = { + fifteenMinutes: { + long: 0, + none: 0, + short: 0, + }, + fiveMinutes: { + long: 0, + none: 0, + short: 0, + }, + fourHours: { + long: 0, + none: 0, + short: 0, + }, + oneDay: { + long: 0, + none: 0, + short: 0, + }, + oneHour: { + long: 0, + none: 0, + short: 0, + }, + } + + if (spotlights) { + spotlights?.forEach((s) => { + s.tickerSignals.forEach((t) => { + const fiveMinutesSummary = GetSummaryChartItem( + t.fiveMinutes[t.fiveMinutes.length - 1] + ) + summary.fiveMinutes.long += fiveMinutesSummary.long + summary.fiveMinutes.short += fiveMinutesSummary.short + summary.fiveMinutes.none += fiveMinutesSummary.none + const fifteenMinutesSummary = GetSummaryChartItem( + t.fifteenMinutes[t.fifteenMinutes.length - 1] + ) + summary.fifteenMinutes.long += fifteenMinutesSummary.long + summary.fifteenMinutes.short += fifteenMinutesSummary.short + summary.fifteenMinutes.none += fifteenMinutesSummary.none + + const oneHour = GetSummaryChartItem(t.oneHour[t.oneHour.length - 1]) + summary.oneHour.long += oneHour.long + summary.oneHour.short += oneHour.short + summary.oneHour.none += oneHour.none + + const fourHours = GetSummaryChartItem( + t.fourHour[t.fourHour.length - 1] + ) + summary.fourHours.long += fourHours.long + summary.fourHours.short += fourHours.short + summary.fourHours.none += fourHours.none + + const oneDay = GetSummaryChartItem(t.oneDay[t.oneDay.length - 1]) + summary.oneDay.long += oneDay.long + summary.oneDay.short += oneDay.short + summary.oneDay.none += oneDay.none + }) + }) + } + + return ( + <> +
    +
    +
    5min
    +
    + +
    +
    +
    +
    15min
    +
    + +
    +
    +
    +
    1h
    +
    + +
    +
    +
    +
    4h
    +
    + +
    +
    +
    +
    1d
    +
    + +
    +
    +
    + + ) + } + + return ( +
    +
    + {GetSignalChart()} +
    + ) +} + +export default SpotlightSummary diff --git a/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightTable.tsx b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightTable.tsx new file mode 100644 index 0000000..cfd3f5c --- /dev/null +++ b/src/Managing.WebApp/src/pages/toolsPage/spotlight/spotlightTable.tsx @@ -0,0 +1,94 @@ +import React from 'react' + +import { Table } from '../../../components/mollecules' +import { SpotLightBadge } from '../../../components/organism' +import type { Signal, Spotlight } from '../../../generated/ManagingApi' +import { TradeDirection } from '../../../generated/ManagingApi' + +interface ISpotlightTable { + spotlight: Spotlight +} +const GetBadgeForTimeframe = (signals: Signal[]) => { + const lastSignal = signals[signals.length > 1 ? signals.length - 1 : 0] + return lastSignal ? ( + + ) : ( + + ) +} + +const SpotlightTable: React.FC = ({ spotlight }) => { + const columns = React.useMemo( + () => [ + { + Header: spotlight.scenario.name, + columns: [ + { + Header: 'Ticker', + accessor: 'ticker', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <>{GetBadgeForTimeframe(cell.row.values.fiveMinutes)} + ), + Header: '5m', + accessor: 'fiveMinutes', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <>{GetBadgeForTimeframe(cell.row.values.fifteenMinutes)} + ), + Header: '15m', + accessor: 'fifteenMinutes', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <>{GetBadgeForTimeframe(cell.row.values.oneHour)} + ), + Header: '1h', + accessor: 'oneHour', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <>{GetBadgeForTimeframe(cell.row.values.fourHour)} + ), + Header: '4h', + accessor: 'fourHour', + disableFilters: true, + disableSortBy: true, + }, + { + Cell: ({ cell }: any) => ( + <>{GetBadgeForTimeframe(cell.row.values.oneDay)} + ), + Header: '1D', + accessor: 'oneDay', + disableFilters: true, + disableSortBy: true, + }, + ], + }, + ], + [] + ) + + return ( +
    +
    + + ) +} + +export default SpotlightTable diff --git a/src/Managing.WebApp/src/pages/toolsPage/tools.tsx b/src/Managing.WebApp/src/pages/toolsPage/tools.tsx new file mode 100644 index 0000000..94c9839 --- /dev/null +++ b/src/Managing.WebApp/src/pages/toolsPage/tools.tsx @@ -0,0 +1,36 @@ +import { useState, useEffect } from 'react' + +import { Tabs } from '../../components/mollecules' +import type { ITabsType } from '../../global/type' + +import RektFees from './rektFees' +import SpotlightView from './spotlight/spotlight' + +const tabs: ITabsType = [ + { + Component: SpotlightView, + index: 1, + label: 'Spotlight', + }, + { + Component: RektFees, + index: 2, + label: 'RektFees', + }, +] + +const Tools: React.FC = () => { + const [selectedTab, setSelectedTab] = useState(tabs[0].index) + + useEffect(() => {}, []) + + return ( +
    +
    + +
    +
    + ) +} + +export default Tools diff --git a/src/Managing.WebApp/src/pages/web3Page/web3.tsx b/src/Managing.WebApp/src/pages/web3Page/web3.tsx new file mode 100644 index 0000000..fb60113 --- /dev/null +++ b/src/Managing.WebApp/src/pages/web3Page/web3.tsx @@ -0,0 +1,104 @@ +import type { Contract } from 'ethers' +import { ethers } from 'ethers' +import { useRef, useState } from 'react' + +const Web3 = () => { + const moodInputRef = useRef() + const [mood, setMood] = useState() + //@ts-ignore + const provider = new ethers.providers.Web3Provider(window.ethereum, 'ropsten') + const contractAddress = '0x0335e801159Af04b3067bED0aeeaCC86Ece51e19' + const moodAbi = [ + { + constant: true, + inputs: [], + name: 'getMood', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + signature: '0x9d0c1397', + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'string', + name: '_mood', + type: 'string', + }, + ], + name: 'setMood', + outputs: [], + signature: '0x5f3cbff5', + stateMutability: 'nonpayable', + type: 'function', + }, + ] + + let contract: Contract + let signer + + provider.send('eth_requestAccounts', []).then(() => { + provider.listAccounts().then(function (accounts) { + signer = provider.getSigner(accounts[0]) + contract = new ethers.Contract(contractAddress, moodAbi, signer) + }) + }) + + async function getMoodAbi() { + const getMoodPromise = contract.getMood() + const currentMood = await getMoodPromise + setMood(currentMood) + } + + async function setMoodAbi() { + //@ts-ignore + const setMoodPromise = contract.setMood(moodInputRef.current.value) + await setMoodPromise + } + + return ( +
    +
    +

    Web3 Playground

    +
    +
    +

    dApp

    + + + + +
    +
    +

    {mood}

    +
    + +
    +
    +
    +
    +
    +

    NFT

    +
    +
    +
    +
    + ) +} + +export default Web3 diff --git a/src/Managing.WebApp/src/pages/workflow/workflows.tsx b/src/Managing.WebApp/src/pages/workflow/workflows.tsx new file mode 100644 index 0000000..205b728 --- /dev/null +++ b/src/Managing.WebApp/src/pages/workflow/workflows.tsx @@ -0,0 +1,98 @@ +import { useQuery } from '@tanstack/react-query' +import { useEffect, useState } from 'react' +import type { Edge, Node } from 'reactflow' + +import useApiUrlStore from '../../app/store/apiStore' +import { Loader } from '../../components/atoms' +import { Tabs } from '../../components/mollecules' +import { WorkflowCanvas } from '../../components/organism' +import type { + IFlow, + SyntheticFlow, + SyntheticWorkflow, +} from '../../generated/ManagingApi' +import { WorkflowClient } from '../../generated/ManagingApi' +import type { ITabsType, IWorkflow } from '../../global/type' + +const mapWorkflowToTabs = (workflows: SyntheticWorkflow[]): ITabsType => { + return workflows.map((workflow: SyntheticWorkflow, index: number) => { + return { + Component: WorkflowCanvas, + index: index, + label: workflow.name, + props: mapFlowsToNodes(workflow.flows, workflow.name), + } + }) +} + +const mapFlowsToNodes = (flows: SyntheticFlow[], name: string): IWorkflow => { + const nodes: Node[] = [] + const edges: Edge[] = [] + + flows.forEach((flow: SyntheticFlow) => { + nodes.push(mapFlowToNode(flow)) + }) + + for (const node of nodes) { + const childrenNodes = nodes.filter((n) => n.data.parentId == node.data.id) + + if (childrenNodes.length > 0) { + childrenNodes.forEach((childNode) => { + edges.push({ + id: `${node.id}-${childNode.id}`, + source: node.id, + target: childNode.id, + }) + }) + } + } + + return { edges, name: name, nodes } as IWorkflow +} + +const mapFlowToNode = (flow: SyntheticFlow): Node => { + return { + data: flow, + id: flow.id, + position: { x: 0, y: 0 }, + type: flow.type, + } +} + +const Workflows: React.FC = () => { + const [selectedTab, setSelectedTab] = useState(1) + const { apiUrl } = useApiUrlStore() + const client = new WorkflowClient({}, apiUrl) + + const { data, isLoading } = useQuery({ + onSuccess: () => { + setSelectedTab(0) + }, + queryFn: () => client.workflow_GetWorkflows(), + queryKey: ['workflows'], + }) + + useEffect(() => {}, [isLoading]) + + if (isLoading || data == null) { + return + } + + return ( +
    +
    + { + console.log('add button clicked') + }} + /> +
    +
    + ) +} + +export default Workflows diff --git a/src/Managing.WebApp/src/polyfills.ts b/src/Managing.WebApp/src/polyfills.ts new file mode 100644 index 0000000..dc3665c --- /dev/null +++ b/src/Managing.WebApp/src/polyfills.ts @@ -0,0 +1,4 @@ +window.global = window.global ?? window +window.process = window.process ?? { env: {} } + +export {} diff --git a/src/Managing.WebApp/src/smartcontracts/courses/mood.sol b/src/Managing.WebApp/src/smartcontracts/courses/mood.sol new file mode 100644 index 0000000..68f90b7 --- /dev/null +++ b/src/Managing.WebApp/src/smartcontracts/courses/mood.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.1; + +contract MoodDiary{ + string mood; + + //create a function that writes a mood to the smart contract + function setMood(string memory _mood) public{ + mood = _mood; + } + + //create a function the reads the mood from the smart contract + function getMood() public view returns(string memory){ + return mood; + } + } \ No newline at end of file diff --git a/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol b/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol new file mode 100644 index 0000000..378a154 --- /dev/null +++ b/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Import the openzepplin contracts +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// GameItem is ERC721 signifies that the contract we are creating imports ERC721 and follows ERC721 contract from openzeppelin +contract GameItem is ERC721 { + + constructor() ERC721("GameItem", "ITM") { + // mint an NFT to yourself + _mint(msg.sender, 1); + } +} \ No newline at end of file diff --git a/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol b/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol new file mode 100644 index 0000000..64743a8 --- /dev/null +++ b/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; + +contract OdaToken is ERC20 { + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { + _mint(msg.sender, 10 * 10 ** 18); + } +} \ No newline at end of file diff --git a/src/Managing.WebApp/src/stores/store.tsx b/src/Managing.WebApp/src/stores/store.tsx new file mode 100644 index 0000000..81c5d23 --- /dev/null +++ b/src/Managing.WebApp/src/stores/store.tsx @@ -0,0 +1,3 @@ +import { atomWithStorage } from 'jotai/utils' + +export const themeAtom = atomWithStorage('theme', 'black') diff --git a/src/Managing.WebApp/src/styles/app.css b/src/Managing.WebApp/src/styles/app.css new file mode 100644 index 0000000..89065d8 --- /dev/null +++ b/src/Managing.WebApp/src/styles/app.css @@ -0,0 +1,46 @@ +.App-logo { + width: 1.5em; +} + +@media (prefers-reduced-motion: no-preference) { + /* .App-logo { + animation: App-logo-spin infinite 20s linear; + } */ +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +button { + font-size: calc(10px + 2vmin); +} + +.notificationWrapper { + @apply w-96 hover:shadow-none transform-gpu hover:translate-y-1 rounded-xl relative flex flex-row items-center justify-between px-4 py-6 text-white transition-all duration-500 ease-in-out translate-y-0 bg-gray-900 shadow-2xl; +} + +.iconWrapper { + @apply text-xl; +} + +.contentWrapper { + @apply flex flex-col items-start justify-center ml-4 cursor-default; +} + +.contentWrapper h1 { + @apply text-base font-semibold leading-none tracking-wider text-gray-200; +} + +.contentWrapper p { + @apply mt-2 text-sm leading-relaxed tracking-wider text-gray-400; +} + +.closeIcon { + @apply top-2 right-2 absolute text-lg cursor-pointer; +} diff --git a/src/Managing.WebApp/src/styles/globals.css b/src/Managing.WebApp/src/styles/globals.css new file mode 100644 index 0000000..da51981 --- /dev/null +++ b/src/Managing.WebApp/src/styles/globals.css @@ -0,0 +1,23 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + margin: 0; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; + } +} + +@layer utilities { + .layout { + @apply sm:w-11/12 w-10/12 mx-auto; + } +} diff --git a/src/Managing.WebApp/src/vite-env.d.ts b/src/Managing.WebApp/src/vite-env.d.ts new file mode 100644 index 0000000..96204cc --- /dev/null +++ b/src/Managing.WebApp/src/vite-env.d.ts @@ -0,0 +1,3 @@ +/// + +declare type AnyFunction = (...args: any[]) => any diff --git a/src/Managing.WebApp/tailwind.config.js b/src/Managing.WebApp/tailwind.config.js new file mode 100644 index 0000000..c57aa29 --- /dev/null +++ b/src/Managing.WebApp/tailwind.config.js @@ -0,0 +1,12 @@ +module.exports = { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + daisyui: { + themes: true, + }, + plugins: [require('@tailwindcss/typography'), require('daisyui')], + theme: { + container: { + center: true, + }, + }, +} diff --git a/src/Managing.WebApp/tsconfig.json b/src/Managing.WebApp/tsconfig.json new file mode 100644 index 0000000..9f749f4 --- /dev/null +++ b/src/Managing.WebApp/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["./src", "hardhat.config.js"], + "exclude": ["node_modules"] +} diff --git a/src/Managing.WebApp/vite.config.ts b/src/Managing.WebApp/vite.config.ts new file mode 100644 index 0000000..449a7b8 --- /dev/null +++ b/src/Managing.WebApp/vite.config.ts @@ -0,0 +1,20 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + sourcemap: true, + target: 'es2020', + }, + optimizeDeps: { + esbuildOptions: { + target: 'es2020', + }, + }, + plugins: [react()], + publicDir: 'assets', + server: { + host: true, + open: true, + }, +}) diff --git a/src/Managing.sln b/src/Managing.sln new file mode 100644 index 0000000..0a20804 --- /dev/null +++ b/src/Managing.sln @@ -0,0 +1,245 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32126.317 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1-Api", "1-Api", "{A1296069-2816-43D4-882C-516BCB718D03}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2-Application", "2-Application", "{F6774DB0-DF13-4077-BC94-0E67EE105C4C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3-Domain", "3-Domain", "{A12DD713-FC6B-4207-ACE4-3883E7275378}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4-Infrastructure", "4-Infrastructure", "{E453D33B-5C2B-4AA1-834D-2C916EC95FC6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Api", "Managing.Api\Managing.Api.csproj", "{0ABF3894-9AC9-4D6A-AEFB-DF0047904018}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application", "Managing.Application\Managing.Application.csproj", "{5BAD133F-B47B-4270-9C63-97A1C070B4DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5-Shared", "5-Shared", "{D6711C71-A263-4398-8DFF-28E2CD1FE0CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Bootstrap", "Managing.Bootstrap\Managing.Bootstrap.csproj", "{2E1D1E52-703E-403A-B563-A3BDA56469B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Exchanges", "Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj", "{C2ADC412-B001-4ECE-9371-977509FB58FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Common", "Managing.Common\Managing.Common.csproj", "{B097137F-2222-43D5-8398-486CCB45B0B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Domain", "Managing.Domain\Managing.Domain.csproj", "{BFB1C04B-F70B-4691-BD06-442D203A6F4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Core", "Managing.Core\Managing.Core.csproj", "{31EF36F8-C057-4571-B942-EAB1E33331F3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0-Documentation", "0-Documentation", "{CC3542A4-2C56-4BF7-A705-A06C974BECDF}" + ProjectSection(SolutionItems) = preProject + ..\Documentation.md = ..\Documentation.md + ..\README.md = ..\README.md + ..\assets\Todo-v1.txt = ..\assets\Todo-v1.txt + ..\assets\Todo-v2.md = ..\assets\Todo-v2.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6-Tests", "6-Tests", "{8F2ECEA7-5BCA-45DF-B6E3-88AADD7AFD45}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Tests", "Managing.Infrastructure.Tests\Managing.Infrastructure.Tests.csproj", "{7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Storage", "Managing.Infrastructure.Storage\Managing.Infrastructure.Storage.csproj", "{837B12AD-E96C-40CE-9DEE-931442A6C15E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Tests", "Managing.Application.Tests\Managing.Application.Tests.csproj", "{35A05E76-29F6-4DC1-886D-FD69926CB490}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Messengers", "Managing.Infrastructure.Messengers\Managing.Infrastructure.Messengers.csproj", "{AD40302A-27C7-4E9D-B644-C7B141571EAF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Api.Workers", "Managing.Api.Workers\Managing.Api.Workers.csproj", "{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Workers", "Managing.Application.Workers\Managing.Application.Workers.csproj", "{F0BE6092-102B-43C5-9CAB-88EA28AADC2D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Databases", "Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj", "{E6CB238E-8F60-4139-BDE6-31534832198E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Abstractions", "Managing.Application.Abstractions\Managing.Application.Abstractions.csproj", "{283AC491-97C3-49E0-AB17-272EFB4E5A9C}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{185F8899-EF44-4D83-99C1-303751E2249C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Evm", "Managing.Infrastructure.Web3\Managing.Infrastructure.Evm.csproj", "{CDDF92D4-9D2E-4134-BD44-3064D6EF462D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Tools.ABI", "Managing.Tools.ABI\Managing.Tools.ABI.csproj", "{A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29FD3672-88EF-406D-A372-32AC121A6A52}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Debug|x64.ActiveCfg = Debug|x64 + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Debug|x64.Build.0 = Debug|x64 + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Release|Any CPU.Build.0 = Release|Any CPU + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Release|x64.ActiveCfg = Release|x64 + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018}.Release|x64.Build.0 = Release|x64 + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Debug|x64.ActiveCfg = Debug|x64 + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Debug|x64.Build.0 = Debug|x64 + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Release|Any CPU.Build.0 = Release|Any CPU + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Release|x64.ActiveCfg = Release|x64 + {5BAD133F-B47B-4270-9C63-97A1C070B4DD}.Release|x64.Build.0 = Release|x64 + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Debug|x64.ActiveCfg = Debug|x64 + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Debug|x64.Build.0 = Debug|x64 + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Release|Any CPU.Build.0 = Release|Any CPU + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Release|x64.ActiveCfg = Release|x64 + {2E1D1E52-703E-403A-B563-A3BDA56469B6}.Release|x64.Build.0 = Release|x64 + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Debug|x64.ActiveCfg = Debug|x64 + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Debug|x64.Build.0 = Debug|x64 + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Release|Any CPU.Build.0 = Release|Any CPU + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Release|x64.ActiveCfg = Release|x64 + {C2ADC412-B001-4ECE-9371-977509FB58FB}.Release|x64.Build.0 = Release|x64 + {B097137F-2222-43D5-8398-486CCB45B0B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B097137F-2222-43D5-8398-486CCB45B0B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B097137F-2222-43D5-8398-486CCB45B0B7}.Debug|x64.ActiveCfg = Debug|x64 + {B097137F-2222-43D5-8398-486CCB45B0B7}.Debug|x64.Build.0 = Debug|x64 + {B097137F-2222-43D5-8398-486CCB45B0B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B097137F-2222-43D5-8398-486CCB45B0B7}.Release|Any CPU.Build.0 = Release|Any CPU + {B097137F-2222-43D5-8398-486CCB45B0B7}.Release|x64.ActiveCfg = Release|x64 + {B097137F-2222-43D5-8398-486CCB45B0B7}.Release|x64.Build.0 = Release|x64 + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Debug|x64.ActiveCfg = Debug|x64 + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Debug|x64.Build.0 = Debug|x64 + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Release|Any CPU.Build.0 = Release|Any CPU + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Release|x64.ActiveCfg = Release|x64 + {BFB1C04B-F70B-4691-BD06-442D203A6F4C}.Release|x64.Build.0 = Release|x64 + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Debug|x64.ActiveCfg = Debug|x64 + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Debug|x64.Build.0 = Debug|x64 + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Release|Any CPU.Build.0 = Release|Any CPU + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Release|x64.ActiveCfg = Release|x64 + {31EF36F8-C057-4571-B942-EAB1E33331F3}.Release|x64.Build.0 = Release|x64 + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Debug|x64.ActiveCfg = Debug|x64 + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Debug|x64.Build.0 = Debug|x64 + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Release|Any CPU.Build.0 = Release|Any CPU + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Release|x64.ActiveCfg = Release|x64 + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE}.Release|x64.Build.0 = Release|x64 + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Debug|x64.ActiveCfg = Debug|x64 + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Debug|x64.Build.0 = Debug|x64 + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Release|Any CPU.Build.0 = Release|Any CPU + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Release|x64.ActiveCfg = Release|x64 + {837B12AD-E96C-40CE-9DEE-931442A6C15E}.Release|x64.Build.0 = Release|x64 + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Debug|x64.ActiveCfg = Debug|x64 + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Debug|x64.Build.0 = Debug|x64 + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Release|Any CPU.Build.0 = Release|Any CPU + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Release|x64.ActiveCfg = Release|x64 + {35A05E76-29F6-4DC1-886D-FD69926CB490}.Release|x64.Build.0 = Release|x64 + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Debug|x64.Build.0 = Debug|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|Any CPU.Build.0 = Release|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.ActiveCfg = Release|Any CPU + {AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.Build.0 = Release|Any CPU + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|x64.ActiveCfg = Debug|x64 + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|x64.Build.0 = Debug|x64 + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|Any CPU.Build.0 = Release|Any CPU + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|x64.ActiveCfg = Release|x64 + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|x64.Build.0 = Release|x64 + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Debug|x64.ActiveCfg = Debug|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Debug|x64.Build.0 = Debug|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Release|Any CPU.Build.0 = Release|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Release|x64.ActiveCfg = Release|Any CPU + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D}.Release|x64.Build.0 = Release|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|x64.ActiveCfg = Debug|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|x64.Build.0 = Debug|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Release|Any CPU.Build.0 = Release|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Release|x64.ActiveCfg = Release|Any CPU + {E6CB238E-8F60-4139-BDE6-31534832198E}.Release|x64.Build.0 = Release|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Debug|x64.ActiveCfg = Debug|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Debug|x64.Build.0 = Debug|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Release|Any CPU.Build.0 = Release|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Release|x64.ActiveCfg = Release|Any CPU + {283AC491-97C3-49E0-AB17-272EFB4E5A9C}.Release|x64.Build.0 = Release|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Debug|x64.ActiveCfg = Debug|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Debug|x64.Build.0 = Debug|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Release|Any CPU.Build.0 = Release|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Release|x64.ActiveCfg = Release|Any CPU + {185F8899-EF44-4D83-99C1-303751E2249C}.Release|x64.Build.0 = Release|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Debug|x64.Build.0 = Debug|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Release|Any CPU.Build.0 = Release|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Release|x64.ActiveCfg = Release|Any CPU + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D}.Release|x64.Build.0 = Release|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Debug|x64.Build.0 = Debug|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Release|Any CPU.Build.0 = Release|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Release|x64.ActiveCfg = Release|Any CPU + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0ABF3894-9AC9-4D6A-AEFB-DF0047904018} = {A1296069-2816-43D4-882C-516BCB718D03} + {5BAD133F-B47B-4270-9C63-97A1C070B4DD} = {F6774DB0-DF13-4077-BC94-0E67EE105C4C} + {2E1D1E52-703E-403A-B563-A3BDA56469B6} = {D6711C71-A263-4398-8DFF-28E2CD1FE0CE} + {C2ADC412-B001-4ECE-9371-977509FB58FB} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6} + {B097137F-2222-43D5-8398-486CCB45B0B7} = {D6711C71-A263-4398-8DFF-28E2CD1FE0CE} + {BFB1C04B-F70B-4691-BD06-442D203A6F4C} = {A12DD713-FC6B-4207-ACE4-3883E7275378} + {31EF36F8-C057-4571-B942-EAB1E33331F3} = {D6711C71-A263-4398-8DFF-28E2CD1FE0CE} + {7E7BFF3A-E936-4A6E-AC75-D44677C6DADE} = {8F2ECEA7-5BCA-45DF-B6E3-88AADD7AFD45} + {837B12AD-E96C-40CE-9DEE-931442A6C15E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6} + {35A05E76-29F6-4DC1-886D-FD69926CB490} = {8F2ECEA7-5BCA-45DF-B6E3-88AADD7AFD45} + {AD40302A-27C7-4E9D-B644-C7B141571EAF} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6} + {0DC797C2-007C-496E-B4C9-FDBD29D4EF4E} = {A1296069-2816-43D4-882C-516BCB718D03} + {F0BE6092-102B-43C5-9CAB-88EA28AADC2D} = {F6774DB0-DF13-4077-BC94-0E67EE105C4C} + {E6CB238E-8F60-4139-BDE6-31534832198E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6} + {283AC491-97C3-49E0-AB17-272EFB4E5A9C} = {F6774DB0-DF13-4077-BC94-0E67EE105C4C} + {CDDF92D4-9D2E-4134-BD44-3064D6EF462D} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6} + {A1D88DC3-1CF6-4C03-AEEC-30AA37420CE1} = {D6711C71-A263-4398-8DFF-28E2CD1FE0CE} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BD7CA081-CE52-4824-9777-C0562E54F3EA} + EndGlobalSection +EndGlobal diff --git a/src/appsettings.Lowpro.json b/src/appsettings.Lowpro.json new file mode 100644 index 0000000..16ef238 --- /dev/null +++ b/src/appsettings.Lowpro.json @@ -0,0 +1,39 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086", + "Token": "OPjdwQBmKr0zQecJ10IDQ4bt32oOJzmFp687QWWzbGeyH0R-gCA6HnXI_B0oQ_InPmSUXKFje8DSAUPbY0hn-w==", + "Organization": "managing-org" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "ApplicationId": "1134966063075971144", + "PublicKey": "55bc79e483ffa203a1da1e49588c86cf1345dd66d676ab52c73dbf09cc1b4800", + "TokenId": "MTEzNDk2NjA2MzA3NTk3MTE0NA.GWHiDf.2s2qq3XCI2bftfnqm_0ndcou5LQwZPdsCZ9nQc", + + "SignalChannelId": 1134858150667898910, + "TradesChannelId": 1134858092530634864, + "TroublesChannelId": 1134858233031446671, + "CopyTradingChannelId": 1134857874896588881, + "RequestsChannelId": 1018589494968078356, + "LeaderboardChannelId": 1133169725237633095, + "NoobiesboardChannelId": 1133504653485690940, + "ButtonExpirationMinutes": 10 + + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/appsettings.Oda-Sandbox.json b/src/appsettings.Oda-Sandbox.json new file mode 100644 index 0000000..c8c284b --- /dev/null +++ b/src/appsettings.Oda-Sandbox.json @@ -0,0 +1,37 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://managingdb:27017", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "http://influxdb:8086/", + "Organization": "", + "Token": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "Discord": { + "BotActivity": "trading strategies", + "HandleUserAction": true, + "ApplicationId": "", + "PublicKey": "", + "TokenId": "", + "SignalChannelId": 966080506473099314, + "TradesChannelId": 998374177763491851, + "TroublesChannelId": 1015761955321040917, + "CopyTradingChannelId": 1132022887012909126, + "RequestsChannelId": 1018589494968078356, + "ButtonExpirationMinutes": 10 + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/captain-definition b/src/captain-definition new file mode 100644 index 0000000..0d2156e --- /dev/null +++ b/src/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "dockerfilePath": "Managing.Api/Dockerfile" +} diff --git a/src/docker-compose.dcproj b/src/docker-compose.dcproj new file mode 100644 index 0000000..8d956aa --- /dev/null +++ b/src/docker-compose.dcproj @@ -0,0 +1,25 @@ + + + + 2.1 + Linux + 185f8899-ef44-4d83-99c1-303751e2249c + LaunchBrowser + {Scheme}://localhost:{ServicePort} + managing.api + + + + + docker-compose.yml + + + + + + + + + + + \ No newline at end of file diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml new file mode 100644 index 0000000..1d050d9 --- /dev/null +++ b/src/docker-compose.override.yml @@ -0,0 +1,29 @@ +version: '3.4' + +services: + + managing.api: + environment: + - ASPNETCORE_ENVIRONMENT=Oda-docker + - ASPNETCORE_URLS=https://+:443;http://+:80 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/src/managing_cert.pfx + ports: + - "82:80" + - "446:443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro + + managing.api.workers: + environment: + - ASPNETCORE_ENVIRONMENT=Oda-docker + - ASPNETCORE_URLS=https://+:443;http://+:80 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/src/managing_cert.pfx + ports: + - "83:80" + - "447:443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro diff --git a/src/docker-compose.yml b/src/docker-compose.yml new file mode 100644 index 0000000..4366a7d --- /dev/null +++ b/src/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.4' +name: dev + +services: + managing.api: + image: ${DOCKER_REGISTRY-}managingapi + build: + context: . + dockerfile: Managing.Api/Dockerfile + networks: + - managing-network + + managing.api.workers: + image: ${DOCKER_REGISTRY-}managingapiworkers + build: + context: . + dockerfile: Managing.Api.Workers/Dockerfile + networks: + - managing-network + +networks: + managing-network: + external: + name: managing-network \ No newline at end of file diff --git a/src/launchSettings.json b/src/launchSettings.json new file mode 100644 index 0000000..2e111a2 --- /dev/null +++ b/src/launchSettings.json @@ -0,0 +1,28 @@ +{ + "profiles": { + "Docker - Api": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "managing.api": "StartDebugging", + "managing.api.workers": "DoNotStart" + } + }, + "Docker - Workers": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "managing.api": "DoNotStart", + "managing.api.workers": "StartDebugging" + } + }, + "Docker - Debug all": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "managing.api": "StartDebugging", + "managing.api.workers": "StartDebugging" + } + } + } +} \ No newline at end of file