docker files fixes from liaqat

This commit is contained in:
alirehmani
2024-05-03 16:39:25 +05:00
commit 464a8730e8
587 changed files with 44288 additions and 0 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
LICENSE
README.md

35
.github/workflows/caprover.yml vendored Normal file
View File

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

379
.gitignore vendored Normal file
View File

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

61
Documentation.md Normal file
View File

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

129
README.md Normal file
View File

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

75
assets/NSwagConfig.nswag Normal file
View File

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

151
assets/Todo-v1.txt Normal file
View File

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

199
assets/Todo-v2.md Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

BIN
assets/img/Architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
assets/img/doc-docker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
assets/img/doc-influxdb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/img/doc-settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

4
captain-definition Normal file
View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "src/Managing.Api/Dockerfil"
}

13
scripts/build_and_run.sh Normal file
View File

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

View File

@@ -0,0 +1,4 @@
cd ..
cd .\src\Managing.WebApp\
npm run lint:fix
npm run prettier

View File

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

View File

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

View File

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

1
src/.dockerignore Normal file
View File

@@ -0,0 +1 @@

4
src/.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# IDE0008: Use explicit type
dotnet_diagnostic.IDE0008.severity = none

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<UserSecretsId>3900ce93-de15-49e5-9a61-7dc2209939ca</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Lowpro.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-sandbox.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -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<List<Worker>> GetWorkers()
{
return Ok(_workerService.GetWorkers());
}
[HttpPatch]
public async Task<ActionResult> ToggleWorker(WorkerType workerType)
{
return Ok(await _workerService.ToggleWorker(workerType));
}
}

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<UserSecretsId>3900ce93-de15-49e5-9a61-7dc2209939ca</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Lowpro.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-docker.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -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<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(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<string>(), 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<EnumSchemaFilter>();
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<FeeWorker>();
builder.Services.AddHostedService<PositionManagerWorker>();
builder.Services.AddHostedService<PositionFetcher>();
builder.Services.AddHostedService<PricesFiveMinutesWorker>();
builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
builder.Services.AddHostedService<PricesOneHourWorker>();
builder.Services.AddHostedService<PricesFourHoursWorker>();
builder.Services.AddHostedService<PricesOneDayWorker>();
builder.Services.AddHostedService<PositionManagerWorker>();
builder.Services.AddHostedService<SpotlightWorker>();
builder.Services.AddHostedService<TraderWatcher>();
builder.Services.AddHostedService<LeaderboardWorker>();
builder.Services.AddHostedService<NoobiesboardWorker>();
// 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>("/positionhub");
});
app.Run();

View File

@@ -0,0 +1,75 @@

using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers;
public abstract class BaseWorker<T> : BackgroundService where T : class
{
private readonly WorkerType _workerType;
protected readonly ILogger<T> _logger;
protected readonly TimeSpan _delay;
private readonly IWorkerService _workerService;
private int _executionCount;
protected BaseWorker(
WorkerType workerType,
ILogger<T> 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);
}

View File

@@ -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<FeeWorker>
{
private readonly ITradingService _tradingService;
private static readonly WorkerType _workerType = WorkerType.Fee;
public FeeWorker(
ILogger<FeeWorker> 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);
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class LeaderboardWorker : BaseWorker<FeeWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.LeaderboardWorker;
public LeaderboardWorker(
ILogger<FeeWorker> logger,
IStatisticService statisticService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(24),
workerService
)
{
_statisticService = statisticService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _statisticService.UpdateLeaderboard();
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class NoobiesboardWorker : BaseWorker<FeeWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.Noobiesboard;
public NoobiesboardWorker(
ILogger<FeeWorker> logger,
IStatisticService statisticService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(24),
workerService
)
{
_statisticService = statisticService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _statisticService.UpdateNoobiesboard();
}
}

View File

@@ -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<PositionFetcher>
{
private static readonly WorkerType _workerType = WorkerType.PositionFetcher;
private readonly ITradingService _tradingService;
private readonly IHubContext<PositionHub> _hubContext;
private readonly ILogger<PositionFetcher> _logger;
public PositionFetcher(
ILogger<PositionFetcher> logger,
IWorkerService workerService,
ITradingService tradingService,
IHubContext<PositionHub> 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);
}
}

View File

@@ -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<PositionManagerWorker>
{
private static readonly WorkerType _workerType = WorkerType.PositionManager;
private readonly ITradingService _tradingService;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ILogger<PositionManagerWorker> _logger;
public PositionManagerWorker(
ILogger<PositionManagerWorker> 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<Position> 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);
}
}
}

View File

@@ -0,0 +1,40 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
{
private readonly IPricesService _pricesService;
private readonly IStatisticService _statisticService;
private readonly Timeframe _timeframe;
public PricesBaseWorker(
ILogger<T> 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);
}
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFifteenMinutesWorker : PricesBaseWorker<PricesFifteenMinutesWorker>
{
public PricesFifteenMinutesWorker(
ILogger<PricesFifteenMinutesWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(1),
WorkerType.PriceFifteenMinutes,
Timeframe.FifteenMinutes
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFiveMinutesWorker : PricesBaseWorker<PricesFiveMinutesWorker>
{
public PricesFiveMinutesWorker(
ILogger<PricesFiveMinutesWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(2.5),
WorkerType.PriceFiveMinutes,
Timeframe.FiveMinutes
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFourHoursWorker : PricesBaseWorker<PricesFourHoursWorker>
{
public PricesFourHoursWorker(
ILogger<PricesFourHoursWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromHours(2),
WorkerType.PriceFourHour,
Timeframe.FourHour
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesOneDayWorker : PricesBaseWorker<PricesOneDayWorker>
{
public PricesOneDayWorker(
ILogger<PricesOneDayWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromHours(12),
WorkerType.PriceOneDay,
Timeframe.OneDay
)
{
}
}

View File

@@ -0,0 +1,22 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesOneHourWorker : PricesBaseWorker<PricesOneHourWorker>
{
public PricesOneHourWorker(
ILogger<PricesOneHourWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(30),
WorkerType.PriceOneHour,
Timeframe.OneHour)
{
}
}

View File

@@ -0,0 +1,34 @@
using Managing.Application.Workers.Abstractions;
using Managing.Common;
namespace Managing.Api.Workers.Workers;
public class SpotlightWorker : BaseWorker<SpotlightWorker>
{
private readonly IStatisticService _statisticService;
public SpotlightWorker(
ILogger<SpotlightWorker> 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;
}
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class TopVolumeTickerWorker : BaseWorker<TopVolumeTickerWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.TopVolumeTicker;
public TopVolumeTickerWorker(
ILogger<TopVolumeTickerWorker> 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);
}
}

View File

@@ -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<FeeWorker>
{
private readonly ITradingService _tradingService;
private static readonly WorkerType _workerType = WorkerType.TraderWatcher;
public TraderWatcher(
ILogger<FeeWorker> logger,
ITradingService tradingService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromSeconds(120),
workerService
)
{
_tradingService = tradingService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _tradingService.WatchTrader();
}
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

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

View File

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

View File

@@ -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<ActionResult<Account>> PostAccount(Account Account)
{
var user = await GetUser();
return Ok(await _AccountService.CreateAccount(user, Account));
}
[HttpGet]
[Route("accounts")]
public async Task<ActionResult<IEnumerable<Account>>> GetAccounts()
{
var user = await GetUser();
return Ok(_AccountService.GetAccountsByUser(user, true));
}
[HttpGet]
[Route("balances")]
public async Task<ActionResult<IEnumerable<Account>>> GetAccountsBalances()
{
var user = await GetUser();
return Ok(_AccountService.GetAccountsBalancesByUser(user));
}
[HttpGet]
public async Task<ActionResult<Account>> 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));
}
}
}

View File

@@ -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<BotHub> _hubContext;
private readonly IBacktester _backtester;
private readonly IScenarioService _scenarioService;
private readonly IAccountService _accountService;
private readonly IMoneyManagementService _moneyManagementService;
public BacktestController(
IHubContext<BotHub> hubContext,
IBacktester backtester,
IScenarioService scenarioService,
IAccountService accountService,
IMoneyManagementService moneyManagementService)
{
_hubContext = hubContext;
_backtester = backtester;
_scenarioService = scenarioService;
_accountService = accountService;
_moneyManagementService = moneyManagementService;
}
[HttpGet]
public ActionResult<IEnumerable<Backtest>> 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<ActionResult<Backtest>> 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);
}
}
}

View File

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

View File

@@ -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<BotController> _logger;
private readonly IHubContext<BotHub> _hubContext;
private readonly IBacktester _backtester;
public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext, IBacktester backtester)
{
_logger = logger;
_mediator = mediator;
_hubContext = hubContext;
_backtester = backtester;
}
[HttpPost]
[Route("Start")]
public async Task<ActionResult<string>> Start(StartBotRequest request)
{
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, request.IsForWatchOnly));
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
[Route("Stop")]
public async Task<ActionResult<string>> Stop(BotType botType, string botName)
{
var result = await _mediator.Send(new StopBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpDelete]
[Route("Delete")]
public async Task<ActionResult<bool>> 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<string> 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<ActionResult<string>> Restart(BotType botType, string botName)
{
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
[Route("RestartAll")]
public async Task<string> 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<ActionResult<string>> 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<List<TradingBot>> GetActiveBots()
{
return await GetBotList();
}
private async Task<List<TradingBot>> GetBotList()
{
var result = await _mediator.Send(new GetActiveBotsCommand());
var list = new List<TradingBot>();
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);
}
}

View File

@@ -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<CandleHub> _hubContext;
public DataController(
IExchangeService exchangeService,
IAccountService accountService,
ICacheService cacheService,
IStatisticService statisticService,
IHubContext<CandleHub> hubContext)
{
_exchangeService = exchangeService;
_accountService = accountService;
_cacheService = cacheService;
_statisticService = statisticService;
_hubContext = hubContext;
}
[HttpPost("GetTickers")]
public async Task<ActionResult<Ticker[]>> 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<SpotlightOverview> 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<ActionResult<List<Candle>>> GetCandles(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe)
{
return Ok(await _exchangeService.GetCandlesInflux(exchange, ticker, startDate, timeframe));
}
}

View File

@@ -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<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
{
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement));
}
[HttpGet]
[Route("moneymanagements")]
public ActionResult<IEnumerable<MoneyManagement>> GetMoneyManagements()
{
return Ok(_moneyManagementService.GetMoneyMangements());
}
[HttpGet]
public ActionResult<MoneyManagement> GetMoneyManagement(string name)
{
return Ok(_moneyManagementService.GetMoneyMangement(name));
}
[HttpDelete]
public ActionResult DeleteMoneyManagement(string name)
{
return Ok(_moneyManagementService.DeleteMoneyManagement(name));
}
}

View File

@@ -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<IEnumerable<Scenario>> GetScenarios()
{
return Ok(_scenarioService.GetScenarios());
}
[HttpPost]
public ActionResult<Scenario> CreateScenario(string name, List<string> strategies)
{
return Ok(_scenarioService.CreateScenario(name, strategies));
}
[HttpDelete]
public ActionResult DeleteScenario(string name)
{
return Ok(_scenarioService.DeleteScenario(name));
}
[HttpGet]
[Route("strategy")]
public ActionResult<IEnumerable<Strategy>> GetStrategies()
{
return Ok(_scenarioService.GetStrategies());
}
[HttpPost]
[Route("strategy")]
public ActionResult<Strategy> 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));
}
}

View File

@@ -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<ActionResult<bool>> ResetSettings()
{
return Ok(await _settingsService.ResetSettings());
}
}

View File

@@ -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<OpenPositionRequest, Position> _openTradeCommandHandler;
private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler;
private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IMediator _mediator;
private readonly ILogger<TradingController> _logger;
public TradingController(
ILogger<TradingController> logger,
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
ITradingService tradingService,
IMediator mediator)
{
_logger = logger;
_openTradeCommandHandler = openTradeCommandHandler;
_closeTradeCommandHandler = closeTradeCommandHandler;
_tradingService = tradingService;
_mediator = mediator;
}
[HttpGet("GetPositions")]
public async Task<ActionResult<List<Position>>> GetPositions(PositionInitiator positionInitiator)
{
var result = await _mediator.Send(new GetPositionsCommand(positionInitiator));
return Ok(result);
}
[HttpGet("GetTrade")]
public async Task<ActionResult<Trade>> 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<ActionResult<Trade>> GetTrades(string accountName, Ticker ticker, string exchangeOrderId)
{
var result = await _mediator.Send(new GetTradesCommand(ticker, accountName));
return Ok(result);
}
[HttpGet("ClosePosition")]
public async Task<ActionResult<Position>> ClosePosition(string identifier)
{
var position = _tradingService.GetPositionByIdentifier(identifier);
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
return Ok(result);
}
[HttpGet("OpenPosition")]
public async Task<ActionResult<Position>> 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);
}
}

View File

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

View File

@@ -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<ActionResult<Workflow>> PostWorkflow([ModelBinder]SyntheticWorkflow workflowRequest)
{
return Ok(await _workflowService.InsertOrUpdateWorkflow(workflowRequest));
}
[HttpGet]
public ActionResult<IEnumerable<SyntheticWorkflow>> GetWorkflows()
{
return Ok(_workflowService.GetWorkflows());
}
[HttpGet]
[Route("flows")]
public async Task<ActionResult<IEnumerable<IFlow>>> GetAvailableFlows()
{
return Ok(await _workflowService.GetAvailableFlows());
}
[HttpDelete]
public ActionResult DeleteWorkflow(string name)
{
return Ok(_workflowService.DeleteWorkflow(name));
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
<UserSecretsId>7476db9f-ade4-414a-a420-e3ab55cb5f8d</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-sandbox.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -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<string> Strategies { get; internal set; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Signal> Signals { get; internal set; }
[Required]
public List<Position> Positions { get; internal set; }
[Required]
public List<Candle> Candles { get; internal set; }
[Required]
public RiskLevel RiskLevel { get; internal set; }
[Required]
public int WinRate { get; internal set; }
[Required]
public decimal ProfitAndLoss { get; internal set; }
[Required]
public Timeframe Timeframe { get; internal set; }
[Required]
public Ticker Ticker { get; internal set; }
[Required]
public string Scenario { get; internal set; }
[Required]
public TradingExchanges Exchange { get; internal set; }
[Required]
public bool IsForWatchingOnly { get; internal set; }
[Required]
public BotType BotType { get; internal set; }
[Required]
public string AccountName { get; internal set; }
[Required]
public MoneyManagement MoneyManagement { get; internal set; }
}
}

168
src/Managing.Api/Program.cs Normal file
View File

@@ -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<Program>();
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<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(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<IJwtUtils, JwtUtils>();
builder.Services.RegisterApiDependencies(builder.Configuration);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document =>
{
document.AddSecurity("JWT", Enumerable.Empty<string>(), 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<EnumSchemaFilter>();
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<JwtMiddleware>();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<BotHub>("/bothub");
endpoints.MapHub<BacktestHub>("/backtesthub");
endpoints.MapHub<CandleHub>("/candlehub");
});
app.Run();

View File

@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Error",
"Microsoft": "Warning"
}
},
"AllowedHosts": "*",
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200/"
}
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -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": "*"
}

View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "Dockerfile"
}

View File

@@ -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"}]}}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using Managing.Domain.Accounts;
namespace Managing.Application.Abstractions.Repositories;
public interface IAccountRepository
{
Task<Account> GetAccountByNameAsync(string name);
Task<Account> GetAccountByKeyAsync(string key);
Task InsertAccountAsync(Account account);
void DeleteAccountByName(string name);
IEnumerable<Account> GetAccounts();
}

View File

@@ -0,0 +1,12 @@

using Managing.Domain.Backtests;
namespace Managing.Application.Abstractions;
public interface IBacktestRepository
{
void InsertBacktest(Backtest result);
IEnumerable<Backtest> GetBacktests();
void DeleteBacktestById(string id);
void DeleteAllBacktests();
}

View File

@@ -0,0 +1,18 @@
using Managing.Common;
using Managing.Domain.Candles;
namespace Managing.Application.Abstractions.Repositories;
public interface ICandleRepository
{
Task<IList<Candle>> GetCandles(
Enums.TradingExchanges exchange,
Enums.Ticker ticker,
Enums.Timeframe timeframe,
DateTime start);
Task<IList<Enums.Ticker>> GetTickersAsync(
Enums.TradingExchanges exchange,
Enums.Timeframe timeframe,
DateTime start);
void InsertCandle(Candle candle);
}

View File

@@ -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<decimal> GetAddressBalance(string address);
Task<DateTime> GetBlockDate(int blockNumber);
Task<List<Holder>> GetContractHolders(string contractAddress, DateTime since);
string SignMessage(string message, string privateKey);
string VerifySignature(string signature, string message);
Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize, string publicAddress);
Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress);
Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe interval);
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker);
Task<bool> InitAddress(string chainName, string publicAddress, string privateKey);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, string receiverAddress);
Task<EvmBalance> GetTokenBalance(string chainName, Ticker ticker, string publicAddress);
Task<bool> CancelOrders(Account account, Ticker ticker);
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = 1);
Task<Trade> GetTrade(Account account, string chainName, Ticker ticker);
Task<Trade> DecreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker);
Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<decimal> GetFee(string chainName);
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
}

View File

@@ -0,0 +1,13 @@
using Managing.Domain.MoneyManagements;
namespace Managing.Application.Abstractions.Repositories;
public interface ISettingsRepository
{
Task<MoneyManagement> GetMoneyManagement(string name);
Task InsertMoneyManagement(MoneyManagement request);
void UpdateMoneyManagement(MoneyManagement moneyManagement);
IEnumerable<MoneyManagement> GetMoneyManagements();
void DeleteMoneyManagement(string name);
void DeleteMoneyManagements();
}

View File

@@ -0,0 +1,20 @@
using Managing.Domain.Statistics;
namespace Managing.Application.Abstractions.Repositories;
public interface IStatisticRepository
{
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
Task SaveSpotligthtOverview(SpotlightOverview overview);
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
void UpdateSpotlightOverview(SpotlightOverview overview);
List<Trader> GetBestTraders();
void UpdateBestTrader(Trader trader);
Task InsertBestTrader(Trader trader);
Task RemoveBestTrader(Trader trader);
List<Trader> GetBadTraders();
void UpdateBadTrader(Trader trader);
Task InsertBadTrader(Trader trader);
Task RemoveBadTrader(Trader trader);
}

View File

@@ -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<Scenario> GetScenarios();
IEnumerable<Strategy> GetStrategies();
void DeleteScenario(string name);
void DeleteStrategy(string name);
void DeleteScenarios();
void DeleteStrategies();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
Fee GetFee(TradingExchanges exchange);
void InsertFee(Fee fee);
void UpdateFee(Fee fee);
}

View File

@@ -0,0 +1,9 @@
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Repositories;
public interface IUserRepository
{
Task<User> GetUserByNameAsync(string name);
Task InsertUserAsync(User user);
}

View File

@@ -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<Worker> GetWorkerAsync(Enums.WorkerType workerType);
IEnumerable<Worker> GetWorkers();
Task InsertWorker(Worker worker);
Task UpdateWorker(Enums.WorkerType workerType, int executionCount);
}

View File

@@ -0,0 +1,12 @@
using Managing.Domain.Workflows.Synthetics;
namespace Managing.Application.Abstractions.Repositories;
public interface IWorkflowRepository
{
bool DeleteWorkflow(string name);
Task<SyntheticWorkflow> GetWorkflow(string name);
IEnumerable<SyntheticWorkflow> GetWorkflows();
Task InsertWorkflow(SyntheticWorkflow workflow);
Task UpdateWorkflow(SyntheticWorkflow workflow);
}

View File

@@ -0,0 +1,16 @@
using Managing.Domain.Accounts;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Services;
public interface IAccountService
{
Task<Account> CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name);
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance);
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
}

View File

@@ -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<Backtest> GetBacktests();
bool DeleteBacktest(string id);
bool DeleteBacktests();
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List<Candle> candles, decimal balance);
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List<Candle> candles, decimal balance);
}
}

View File

@@ -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<Trader> traders);
Task SendBadTraders(List<Trader> traders);
}

View File

@@ -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<Trade> OpenTrade(
Account account,
Ticker ticker,
TradeDirection direction,
decimal price,
decimal quantity,
decimal? leverage = null,
TradeType tradeType = TradeType.Limit,
bool reduceOnly = false,
bool isForPaperTrading = false,
DateTime? currentDate = null,
bool ioc = true);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
decimal GetPrice(Account account, Ticker ticker, DateTime date);
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice,
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
Task<List<Ticker>> GetTickers(Account account, Timeframe timeframe);
Task<Trade> OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice,
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false);
decimal GetVolume(Account account, Ticker ticker);
Task<List<Trade>> GetTrades(Account account, Ticker ticker);
Task<bool> CancelOrder(Account account, Ticker ticker);
decimal GetFee(Account account, bool isForPaperTrading = false);
Candle GetCandle(Account account, Ticker ticker, DateTime date);
Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
Task<List<Candle>> 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<List<Trade>> GetOpenOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string orderId, Ticker ticker);
}

Some files were not shown because too many files have changed in this diff Show More