├── .gitignore ├── README.md ├── screenshots └── Mainscreen.png └── src ├── 3Commas.BotCreator.Tests ├── 3Commas.BotCreator.Tests.csproj ├── AutomapperTests.cs ├── BinanceTests.cs ├── BotManagerTests.cs └── NameHelperTests.cs ├── 3Commas.BotCreator.sln └── 3Commas.BotCreator ├── 3Commas.BotCreator.csproj ├── 3CommasLayer ├── IXCommasClient.cs └── Implementations │ └── XCommasClient.cs ├── App.config ├── ExchangeLayer ├── Entities │ ├── Pair.cs │ └── PlaceOrderResult.cs ├── IExchange.cs └── Implementations │ ├── Binance.cs │ └── Huobi.cs ├── Infrastructure ├── IViewBase.cs └── Presenter.cs ├── Keys.cs ├── MappingProfiles └── BotSettingsProfile.cs ├── Misc ├── AssemblyHelper.cs ├── BotManager.cs ├── ControlHelper.cs ├── NameHelper.cs └── TextBoxLogger.cs ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── DataSources │ └── _3Commas.BotCreator.Views.MainForm.BotSettings.datasource ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Resources ├── 3Commas.png ├── Add_16x16.png ├── Binance.png ├── Cancel_16x16.png ├── Clear_16x16.png ├── Close_16x16.png ├── Huobi.png ├── Info_16x16.png ├── Info_32x32.png ├── Play_16x16.png ├── Properties_32x32.png ├── Save_16x16.png ├── ShowTestReport_16x16.png ├── Suggestion_16x16.png └── download.png ├── Services ├── BotSettingService │ ├── BotSettingService.cs │ ├── BotSettings.cs │ └── IBotSettingService.cs └── MessageBoxService │ ├── IMessageBoxService.cs │ └── MessageBoxService.cs ├── Views ├── AboutBox │ ├── AboutBox.Designer.cs │ ├── AboutBox.cs │ └── AboutBox.resx ├── ChooseSignalView │ ├── ChooseSignalView.Designer.cs │ ├── ChooseSignalView.cs │ └── ChooseSignalView.resx ├── MainForm │ ├── BotSettingViewModel.cs │ ├── IMainForm.cs │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ ├── MainForm.resx │ └── MainFormPresenter.cs ├── SaveTemplateView │ ├── SaveTemplateView.Designer.cs │ ├── SaveTemplateView.cs │ └── SaveTemplateView.resx └── SetApiKeyView │ ├── SetApiKeyView.Designer.cs │ ├── SetApiKeyView.cs │ └── SetApiKeyView.resx ├── packages.config └── robot.ico /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3Commas.BotCreator 2 | 3 | This unofficial tool makes it possible to create several simple DCA bots at once. 4 | 5 | **Note that only this use case is covered:** 6 | 7 | I needed something that would allow me to create multiple simple DCA bots with the same quote currency. It will automatically find the next base currency ordered by Volume descending. 8 | If short bots are created, the tool can buy base coins for you directly on your exchange (if desired). 9 | 10 | 11 | 12 | --- 13 | 14 | ## Technical description 15 | 16 | The implementation is based on .Net Framework, since the View Designer was not yet productive for NET5 at this point in time. 17 | 18 | Therefore I created the tool more or less quick & dirty because I needed this functionality quickly. Time was money in this case :) 19 | 20 | Implementation is build upon the CryptoExchange.Net, 3Commas.Net, Binance.Net and Huobi.Net libraries. Thanks for the brilliant work! 21 | 22 | The only use cases available are the ones I needed for myself. But if you need something, [let me know](https://github.com/MarcDrexler/3Commas.BotCreator/issues). 23 | 24 | Also if you think something is broken or have any questions, please open an [Issue](https://github.com/MarcDrexler/3Commas.BotCreator/issues). 25 | 26 | ## Features 27 | 28 | - Add as many bots as you want 29 | - Uses pairs at the highest volume first 30 | - Skip bot creation if base is stablecoin 31 | - Skip bot creation for blacklisted pairs 32 | - Skip bot creation for existing pair + strategy 33 | - This way you can create 10 Bots, and another day the next 10 Bots without having to worry about duplicates. 34 | - Specify your own name schema. There are existing placeholders for Strategy and Pair. 35 | - Templates 36 | - Signals currently available: Manual, Nonstop, TradingView, RSI, ULT, TA Presets, Custom 37 | - Buys tokens for your new Short bots via market buy order (Binance & Huobi only) 38 | - Click the Preview button to see what happens when you hit the Create button :) 39 | 40 | ### Excluded Pairs 41 | 42 | All pairs that end with UP, DOWN, BEAR and BULL are skipped. 43 | 44 | These are currently hard coded. 45 | 46 | ## Screenshots 47 | 48 | ![Main Screen](https://github.com/MarcDrexler/3Commas.BotCreator/blob/master/screenshots/Mainscreen.png) 49 | 50 | ## Prerequisites 51 | 52 | - .NET Framework 4.7.2 (which already might be installed on your Windows machine) 53 | - 3Commas API key and secret 54 | - Exchange API key and secret (Binance or Huobi) 55 | 56 | **Note:** By default, API keys are not stored on your computer and must be entered again the next time the application is started. 57 | There is an option to persist ApiKey and Secret. However, use is at your own risk. Other programs could read this information. 58 | 59 | ### Why API Keys for my Exchange? 60 | 61 | The app will find suitable pairs for you. It simply queries your exchange for pairs and ranks them in descending order of volume. 62 | If you want to create short bots and don't have tokens for the new pairs, tokens are automatically bought for you (if you choose to). 63 | 64 | ## Installer 65 | 66 | I use MS ClickOnce for installation and updates. 67 | 68 | An installer with the current version is hosted [here](https://marcdrexler.blob.core.windows.net/botcreator/BotCreator.application) 69 | 70 | Updates are automatically checked for each start. 71 | 72 | Because the package is not signed with a public certificate, you might see some security warnings when installing and starting the app if you are using Windows 7 or later. 73 | 74 | ## Support 75 | 76 | I develop and maintain this package on my own for free in my spare time. 77 | Donations are greatly appreciated and a motivation to keep improving. 78 | 79 | XMR: 89rmrxDAGAWWhSZXhnNf335qYfyXz4vQsNAM1VFSg6Y7Tve9BGF9pwte9ps61E9FY76r4onhWw7e19eu7fM8BARQMRHNBt7 80 | 81 | or 82 | 83 | Buy Me A Coffee 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /screenshots/Mainscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/screenshots/Mainscreen.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator.Tests/3Commas.BotCreator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | _3Commas.BotCreator.Tests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator.Tests/AutomapperTests.cs: -------------------------------------------------------------------------------- 1 | using _3Commas.BotCreator.MappingProfiles; 2 | using AutoMapper; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace _3Commas.BotCreator.Tests 6 | { 7 | [TestClass] 8 | public class AutomapperTests 9 | { 10 | [TestMethod] 11 | public void AutoMapper_Configuration_IsValid() 12 | { 13 | var config = new MapperConfiguration(cfg => cfg.AddProfile()); 14 | config.AssertConfigurationIsValid(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator.Tests/BinanceTests.cs: -------------------------------------------------------------------------------- 1 | using _3Commas.BotCreator.ExchangeLayer.Implementations; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace _3Commas.BotCreator.Tests 5 | { 6 | [TestClass] 7 | public class BinanceTests 8 | { 9 | [TestMethod] 10 | public void ExtractBaseCurrencyTest() 11 | { 12 | var actual = new Binance(FakeKeys).ExtractBaseCurrency("usdtbnb", "usdt"); 13 | 14 | Assert.AreEqual("BNB", actual); 15 | } 16 | 17 | [TestMethod] 18 | public void ToBinanceSymbolTest() 19 | { 20 | var actual = new Binance(FakeKeys).ToBinanceSymbol("usdt", "bnb"); 21 | 22 | Assert.AreEqual("BNBUSDT", actual); 23 | } 24 | 25 | public Keys FakeKeys => new Keys 26 | { 27 | ApiKey3Commas = "X", 28 | ApiKeyBinance = "X", 29 | Secret3Commas = "X", 30 | SecretBinance = "X" 31 | }; 32 | } 33 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator.Tests/BotManagerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using _3Commas.BotCreator._3CommasLayer; 5 | using _3Commas.BotCreator.ExchangeLayer; 6 | using _3Commas.BotCreator.ExchangeLayer.Entities; 7 | using _3Commas.BotCreator.Misc; 8 | using _3Commas.BotCreator.Views.MainForm; 9 | using Microsoft.Extensions.Logging.Abstractions; 10 | using Microsoft.VisualStudio.TestTools.UnitTesting; 11 | using Moq; 12 | using XCommas.Net; 13 | using XCommas.Net.Objects; 14 | 15 | namespace _3Commas.BotCreator.Tests 16 | { 17 | [TestClass] 18 | public class BotManagerTests 19 | { 20 | [TestMethod] 21 | public async Task CreateBots_Create5Bots_5BotsCreated() 22 | { 23 | // Arrange 24 | var exchange = BuildExchangeWith5Pairs(); 25 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 26 | var request = GetDefaultRequest(); 27 | 28 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 29 | 30 | // Act 31 | await target.CreateBots(5, false, request, CancellationToken.None); 32 | 33 | // Assert 34 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(5)); 35 | } 36 | 37 | [TestMethod] 38 | public async Task CreateBots_CreateMoreBotsThanPairsAvailable_5BotsCreated() 39 | { 40 | // Arrange 41 | var exchange = BuildExchangeWith5Pairs(); 42 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 43 | var request = GetDefaultRequest(); 44 | 45 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 46 | 47 | // Act 48 | await target.CreateBots(10, false, request, CancellationToken.None); 49 | 50 | // Assert 51 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(5)); 52 | } 53 | 54 | [TestMethod] 55 | public async Task CreateBots_Disable_WillNotEnableBots() 56 | { 57 | // Arrange 58 | var exchange = BuildExchangeWith5Pairs(); 59 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 60 | var request = GetDefaultRequest(); 61 | 62 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 63 | 64 | // Act 65 | await target.CreateBots(5, false, request, CancellationToken.None); 66 | 67 | // Assert 68 | xCommasClient.Verify(x => x.EnableBotAsync(It.IsAny()), Times.Never); 69 | } 70 | 71 | [TestMethod] 72 | public async Task CreateBots_Enable_WillEnableBots() 73 | { 74 | // Arrange 75 | var exchange = BuildExchangeWith5Pairs(); 76 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 77 | var request = GetDefaultRequest(); 78 | 79 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 80 | 81 | // Act 82 | await target.CreateBots(5, true, request, CancellationToken.None); 83 | 84 | // Assert 85 | xCommasClient.Verify(x => x.EnableBotAsync(It.IsAny()), Times.Exactly(5)); 86 | } 87 | 88 | [TestMethod] 89 | public async Task CreateBots_CheckForExistingBots_4BotsCreated() 90 | { 91 | // Arrange 92 | var exchange = BuildExchangeWith5Pairs(); 93 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 94 | var request = GetDefaultRequest(); 95 | request.SkipExistingPairs = true; 96 | 97 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 98 | 99 | // Act 100 | await target.CreateBots(5, false, request, CancellationToken.None); 101 | 102 | // Assert 103 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(4)); 104 | } 105 | 106 | [TestMethod] 107 | public async Task CreateBots_CheckForBlacklistedPairs_4BotsCreated() 108 | { 109 | // Arrange 110 | var exchange = BuildExchangeWith5Pairs(); 111 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 112 | var request = GetDefaultRequest(); 113 | request.SkipBlacklistedPairs = true; 114 | 115 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 116 | 117 | // Act 118 | await target.CreateBots(5, false, request, CancellationToken.None); 119 | 120 | // Assert 121 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(4)); 122 | } 123 | 124 | [TestMethod] 125 | public async Task CreateBots_CheckForBaseStablecoins_4BotsCreated() 126 | { 127 | // Arrange 128 | var exchange = BuildExchangeWith5Pairs(); 129 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 130 | var request = GetDefaultRequest(); 131 | request.SkipBaseStablecoin = true; 132 | 133 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 134 | 135 | // Act 136 | await target.CreateBots(5, false, request, CancellationToken.None); 137 | 138 | // Assert 139 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(4)); 140 | } 141 | 142 | [TestMethod] 143 | public async Task CreateBots_CheckForExistingBotsAndBlacklistedPairsAndBaseStablecoins_2BotsCreated() 144 | { 145 | // Arrange 146 | var exchange = BuildExchangeWith5Pairs(); 147 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 148 | var request = GetDefaultRequest(); 149 | request.SkipExistingPairs = true; 150 | request.SkipBlacklistedPairs = true; 151 | request.SkipBaseStablecoin = true; 152 | 153 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 154 | 155 | // Act 156 | await target.CreateBots(5, false, request, CancellationToken.None); 157 | 158 | // Assert 159 | xCommasClient.Verify(x => x.CreateBotAsync(It.IsAny(), request.Strategy, It.IsAny()), Times.Exactly(2)); 160 | } 161 | 162 | [TestMethod] 163 | public async Task CreateBots_NoBuyOrderWanted_WillNotPlaceBuyOrder() 164 | { 165 | // Arrange 166 | var exchange = BuildExchangeWith5Pairs(); 167 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 168 | var request = GetDefaultRequest(); 169 | 170 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 171 | 172 | // Act 173 | await target.CreateBots(5, false, request, CancellationToken.None); 174 | 175 | // Assert 176 | exchange.Verify(x => x.PlaceOrder(It.IsAny(), It.IsAny()), Times.Never); 177 | } 178 | 179 | [TestMethod] 180 | public async Task CreateBots_BuyOrderWanted_WillPlaceBuyOrder() 181 | { 182 | // Arrange 183 | var exchange = BuildExchangeWith5Pairs(); 184 | var xCommasClient = BuildXCommasClientWith1ExistingAnd1Blacklisted(); 185 | var request = GetDefaultRequest(); 186 | request.BuyBaseCurrency = true; 187 | request.BaseCurrencyToBuy = (decimal) 123.45; 188 | 189 | var target = new BotManager(NullLogger.Instance, xCommasClient.Object, exchange.Object); 190 | 191 | // Act 192 | await target.CreateBots(5, false, request, CancellationToken.None); 193 | 194 | // Assert 195 | exchange.Verify(x => x.PlaceOrder(It.IsAny(), (decimal) 123.45), Times.Exactly(5)); 196 | } 197 | 198 | [TestMethod] 199 | public void TransformPairTo3CommasSymbolStringTest() 200 | { 201 | var actual = BotManager.TransformPairTo3CommasSymbolString("USDT", "BNB"); 202 | 203 | Assert.AreEqual("USDT_BNB", actual); 204 | } 205 | 206 | private Mock BuildXCommasClientWith1ExistingAnd1Blacklisted() 207 | { 208 | var mock = new Mock(); 209 | mock.Setup(x => x.GetBotsAsync(It.IsAny(), 0, Strategy.Long, BotScope.Enabled)).ReturnsAsync(new XCommasResponse(new[] 210 | { 211 | new Bot() {Pairs = new[] {"usdt_btc"}, Strategy = Strategy.Long}, 212 | }, "", "")); 213 | mock.Setup(x => x.GetBotsAsync(It.IsAny(), 100, Strategy.Long, BotScope.Enabled)).ReturnsAsync(new XCommasResponse(new Bot[0], "", "")); 214 | mock.Setup(x => x.GetBotsAsync(It.IsAny(), It.IsAny(), Strategy.Long, BotScope.Disabled)).ReturnsAsync(new XCommasResponse(new Bot[0], "", "")); 215 | mock.Setup(x => x.GetBotsAsync(It.IsAny(), It.IsAny(), Strategy.Short, BotScope.Enabled)).ReturnsAsync(new XCommasResponse(new Bot[0], "", "")); 216 | mock.Setup(x => x.GetBotsAsync(It.IsAny(), It.IsAny(), Strategy.Short, BotScope.Disabled)).ReturnsAsync(new XCommasResponse(new Bot[0], "", "")); 217 | 218 | mock.Setup(x => x.GetBotPairsBlackListAsync()).ReturnsAsync(new XCommasResponse(new BotPairsBlackListData() {Pairs = new[] {"USDT_ETH"}}, "", "")); 219 | 220 | mock.Setup(x => x.CreateBotAsync(1000, It.IsAny(), It.IsAny())).ReturnsAsync(new XCommasResponse(new Bot(), "", "")); 221 | 222 | mock.Setup(x => x.EnableBotAsync(It.IsAny())).ReturnsAsync(new XCommasResponse(new Bot(), "", "")); 223 | 224 | mock.Setup(x => x.GetCurrencyRateAsync(It.IsAny())).ReturnsAsync(new XCommasResponse(new CurrencyRate(), "", "")); 225 | 226 | return mock; 227 | } 228 | 229 | private Mock BuildExchangeWith5Pairs() 230 | { 231 | var exchange = new Mock(); 232 | exchange.Setup(x => x.GetAllPairsByQuoteCurrency("USDT")).ReturnsAsync(new List 233 | { 234 | new Pair {BaseCurrency = "BTC", QuoteCurrency = "USDT", TotalTradedQuoteAssetVolume = 10000000}, 235 | new Pair {BaseCurrency = "ETH", QuoteCurrency = "USDT", TotalTradedQuoteAssetVolume = 100000}, 236 | new Pair {BaseCurrency = "LTC", QuoteCurrency = "USDT", TotalTradedQuoteAssetVolume = 10000}, 237 | new Pair {BaseCurrency = "LINK", QuoteCurrency = "USDT", TotalTradedQuoteAssetVolume = 100}, 238 | new Pair {BaseCurrency = "BUSD", QuoteCurrency = "USDT", TotalTradedQuoteAssetVolume = 10}, 239 | }); 240 | 241 | exchange.Setup(x => x.PlaceOrder(It.IsAny(), (decimal) 123.45)).ReturnsAsync(new PlaceOrderResult() {Success = true}); 242 | 243 | return exchange; 244 | } 245 | 246 | private BotSettingViewModel GetDefaultRequest() => new BotSettingViewModel(LeverageType.NotSpecified, 0, (decimal)0, StopLossType.StopLoss, false, 0, false, false, false, "USDT", Strategy.Long, StartOrderType.Limit, 5, 1, 1, 1, 1, (decimal)1.5, false, 0, "{strategy} {pair} Bot", 10, 11, new List(), 0, 1000, 0); 247 | } 248 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator.Tests/NameHelperTests.cs: -------------------------------------------------------------------------------- 1 | using _3Commas.BotCreator.Misc; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using XCommas.Net.Objects; 4 | 5 | namespace _3Commas.BotCreator.Tests 6 | { 7 | [TestClass] 8 | public class NameHelperTests 9 | { 10 | [TestMethod] 11 | public void GenerateBotNameTest() 12 | { 13 | var actual = NameHelper.GenerateBotName("My new {pair} {strategy} bot", "USDT_BNB", Strategy.Long); 14 | 15 | Assert.AreEqual("My new USDT_BNB Long bot", actual); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30406.217 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3Commas.BotCreator", "3Commas.BotCreator\3Commas.BotCreator.csproj", "{02D16940-5926-45EA-A706-21B8D3E1F643}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3Commas.BotCreator.Tests", "3Commas.BotCreator.Tests\3Commas.BotCreator.Tests.csproj", "{AA38FEBA-8B7D-4E96-934D-B4022E69027F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {02D16940-5926-45EA-A706-21B8D3E1F643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {02D16940-5926-45EA-A706-21B8D3E1F643}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {02D16940-5926-45EA-A706-21B8D3E1F643}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {02D16940-5926-45EA-A706-21B8D3E1F643}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AA38FEBA-8B7D-4E96-934D-B4022E69027F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AA38FEBA-8B7D-4E96-934D-B4022E69027F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AA38FEBA-8B7D-4E96-934D-B4022E69027F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AA38FEBA-8B7D-4E96-934D-B4022E69027F}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {62584358-3810-4548-BCD9-DC2AB25F5DDC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/3CommasLayer/IXCommasClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using XCommas.Net; 3 | using XCommas.Net.Objects; 4 | 5 | namespace _3Commas.BotCreator._3CommasLayer 6 | { 7 | public interface IXCommasClient 8 | { 9 | Task> GetBotPairsBlackListAsync(); 10 | 11 | Task> EnableBotAsync(int botId); 12 | 13 | Task> CreateBotAsync(int accountId, Strategy strategy, BotData botData); 14 | 15 | Task> UpdateBotAsync(int botId, BotUpdateData botUpdateData); 16 | 17 | Task> GetBotsAsync(int limit, int offset); 18 | 19 | Task> GetAccountsAsync(); 20 | 21 | Task> GetCurrencyRateAsync(string symbol); 22 | } 23 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/3CommasLayer/Implementations/XCommasClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using XCommas.Net; 3 | using XCommas.Net.Objects; 4 | 5 | namespace _3Commas.BotCreator._3CommasLayer.Implementations 6 | { 7 | public class XCommasClient : IXCommasClient 8 | { 9 | private readonly XCommasApi _3CommasClient; 10 | 11 | public XCommasClient(Keys settings, bool usePaperTrading) 12 | { 13 | _3CommasClient = new XCommasApi(settings.ApiKey3Commas, settings.Secret3Commas, userMode: usePaperTrading ? UserMode.Paper : UserMode.Real); 14 | } 15 | 16 | public async Task> CreateBotAsync(int accountId, Strategy strategy, BotData botData) 17 | { 18 | return await _3CommasClient.CreateBotAsync(accountId, strategy, botData); 19 | } 20 | 21 | public async Task> UpdateBotAsync(int botId, BotUpdateData botUpdateData) 22 | { 23 | return await _3CommasClient.UpdateBotAsync(botId, botUpdateData); 24 | } 25 | 26 | public async Task> GetBotsAsync(int limit, int offset) 27 | { 28 | return await _3CommasClient.GetBotsAsync(limit, offset); 29 | } 30 | 31 | public async Task> GetAccountsAsync() 32 | { 33 | return await _3CommasClient.GetAccountsAsync(); 34 | } 35 | 36 | public async Task> GetCurrencyRateAsync(string pair) 37 | { 38 | return await _3CommasClient.GetCurrencyRateAsync(pair); 39 | } 40 | 41 | public async Task> GetBotPairsBlackListAsync() 42 | { 43 | return await _3CommasClient.GetBotPairsBlackListAsync(); 44 | } 45 | 46 | public async Task> EnableBotAsync(int botId) 47 | { 48 | return await _3CommasClient.EnableBotAsync(botId); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | <_3Commas.BotCreator.Properties.Settings> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/ExchangeLayer/Entities/Pair.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator.ExchangeLayer.Entities 2 | { 3 | public class Pair 4 | { 5 | public decimal TotalTradedQuoteAssetVolume { get; set; } 6 | public string QuoteCurrency { get; set; } 7 | public string BaseCurrency { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/ExchangeLayer/Entities/PlaceOrderResult.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator.ExchangeLayer.Entities 2 | { 3 | public class PlaceOrderResult 4 | { 5 | public bool Success { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/ExchangeLayer/IExchange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using _3Commas.BotCreator.ExchangeLayer.Entities; 4 | 5 | namespace _3Commas.BotCreator.ExchangeLayer 6 | { 7 | public interface IExchange 8 | { 9 | Task> GetAllPairsByQuoteCurrency(string quoteCurrency); 10 | Task PlaceOrder(Pair pair, decimal amountToBuyInQuoteCurrency); 11 | string Name { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/ExchangeLayer/Implementations/Binance.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using _3Commas.BotCreator.ExchangeLayer.Entities; 5 | using Binance.Net; 6 | using Binance.Net.Enums; 7 | using Binance.Net.Objects.Spot; 8 | using CryptoExchange.Net.Authentication; 9 | 10 | namespace _3Commas.BotCreator.ExchangeLayer.Implementations 11 | { 12 | public class Binance : IExchange 13 | { 14 | public Binance(Keys settings) 15 | { 16 | BinanceClient.SetDefaultOptions(new BinanceClientOptions { ApiCredentials = new ApiCredentials(settings.ApiKeyBinance, settings.SecretBinance) }); 17 | } 18 | 19 | public string Name => "Binance"; 20 | 21 | public async Task> GetAllPairsByQuoteCurrency(string quoteCurrency) 22 | { 23 | using (var binance = new BinanceClient()) 24 | { 25 | return (await binance.Spot.Market.Get24HPricesAsync()).Data.Where(x => x.Symbol.ToLower().Contains(quoteCurrency.ToLower())).Select(x => new Pair 26 | { 27 | TotalTradedQuoteAssetVolume = x.QuoteVolume, 28 | QuoteCurrency = quoteCurrency, 29 | BaseCurrency = ExtractBaseCurrency(x.Symbol, quoteCurrency) 30 | }).ToList(); 31 | } 32 | } 33 | 34 | public async Task PlaceOrder(Pair pair, decimal amountToBuyInQuoteCurrency) 35 | { 36 | var result = new PlaceOrderResult(); 37 | using (var binance = new BinanceClient()) 38 | { 39 | var response = await binance.Spot.Order.PlaceOrderAsync(ToBinanceSymbol(pair.QuoteCurrency, pair.BaseCurrency), OrderSide.Buy, OrderType.Market, quoteOrderQuantity: amountToBuyInQuoteCurrency); 40 | if (response.Success) 41 | { 42 | result.Message = $"Market Buy Order placed: {response.Data.Quantity} {pair.BaseCurrency} / {response.Data.QuoteQuantityFilled} {pair.QuoteCurrency}"; 43 | result.Success = true; 44 | } 45 | else 46 | { 47 | result.Message = $"Error while placing Order: {response.Error?.Message}"; 48 | } 49 | } 50 | return result; 51 | } 52 | 53 | public string ExtractBaseCurrency(string symbol, string quoteCurrency) 54 | { 55 | return symbol.ToUpper().Replace(quoteCurrency.ToUpper(), ""); 56 | } 57 | 58 | public string ToBinanceSymbol(string pairQuoteCurrency, string pairBaseCurrency) 59 | { 60 | return $"{pairBaseCurrency.ToUpper()}{pairQuoteCurrency.ToUpper()}"; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/ExchangeLayer/Implementations/Huobi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using _3Commas.BotCreator.ExchangeLayer.Entities; 5 | using CryptoExchange.Net.Authentication; 6 | using Huobi.Net; 7 | using Huobi.Net.Objects; 8 | 9 | namespace _3Commas.BotCreator.ExchangeLayer.Implementations 10 | { 11 | public class Huobi : IExchange 12 | { 13 | public Huobi(Keys settings) 14 | { 15 | HuobiClient.SetDefaultOptions(new HuobiClientOptions { ApiCredentials = new ApiCredentials(settings.ApiKeyHuobi, settings.SecretHuobi) }); 16 | } 17 | public string Name => "Huobi"; 18 | 19 | public async Task> GetAllPairsByQuoteCurrency(string quoteCurrency) 20 | { 21 | var pairs = new List(); 22 | using (var huobi = new HuobiClient()) 23 | { 24 | var symbols = (await huobi.GetSymbolsAsync()).Data.Where(x => x.Symbol.ToLower().Contains(quoteCurrency.ToLower())).ToList(); 25 | foreach (var huobiSymbol in symbols) 26 | { 27 | if (huobiSymbol.State == HuobiSymbolState.Online) 28 | { 29 | var symbolDetails = await huobi.GetSymbolDetails24HAsync(huobiSymbol.Symbol); 30 | if (symbolDetails.Data == null) 31 | { 32 | continue; 33 | } 34 | 35 | var pair = new Pair 36 | { 37 | TotalTradedQuoteAssetVolume = symbolDetails.Data.Volume.Value, 38 | QuoteCurrency = quoteCurrency, 39 | BaseCurrency = ExtractBaseCurrency(huobiSymbol.Symbol, quoteCurrency) 40 | }; 41 | pairs.Add(pair); 42 | } 43 | } 44 | } 45 | return pairs; 46 | } 47 | 48 | public async Task PlaceOrder(Pair pair, decimal amountToBuyInQuoteCurrency) 49 | { 50 | var result = new PlaceOrderResult(); 51 | using (var huobi = new HuobiClient()) 52 | { 53 | var accounts = await huobi.GetAccountsAsync(); 54 | var response = await huobi.PlaceOrderAsync(accounts.Data.Single().Id, ToHuobiSymbol(pair.QuoteCurrency, pair.BaseCurrency), HuobiOrderType.MarketBuy, amountToBuyInQuoteCurrency); 55 | if (response.Success) 56 | { 57 | result.Message = $"Market Buy Order placed. ID: {response.Data}"; 58 | result.Success = true; 59 | } 60 | else 61 | { 62 | result.Message = $"Error while placing Order: {response.Error?.Message}"; 63 | } 64 | } 65 | return result; 66 | } 67 | 68 | public string ExtractBaseCurrency(string symbol, string quoteCurrency) 69 | { 70 | return symbol.ToUpper().Replace(quoteCurrency.ToUpper(), ""); 71 | } 72 | 73 | public string ToHuobiSymbol(string pairQuoteCurrency, string pairBaseCurrency) 74 | { 75 | return $"{pairBaseCurrency.ToUpper()}{pairQuoteCurrency.ToUpper()}"; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Infrastructure/IViewBase.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace _3Commas.BotCreator.Infrastructure 4 | { 5 | public interface IViewBase : IWin32Window 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Infrastructure/Presenter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace _3Commas.BotCreator.Infrastructure 4 | { 5 | public abstract class PresenterBase where TView: IWin32Window 6 | { 7 | protected PresenterBase(TView view) 8 | { 9 | View = view; 10 | } 11 | 12 | protected TView View { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Keys.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator 2 | { 3 | public class Keys 4 | { 5 | public string ApiKey3Commas { get; set; } 6 | public string Secret3Commas { get; set; } 7 | public string ApiKeyBinance { get; set; } 8 | public string SecretBinance { get; set; } 9 | public string ApiKeyHuobi { get; set; } 10 | public string SecretHuobi { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/MappingProfiles/BotSettingsProfile.cs: -------------------------------------------------------------------------------- 1 | using _3Commas.BotCreator.Services.BotSettingService; 2 | using _3Commas.BotCreator.Views.MainForm; 3 | using AutoMapper; 4 | 5 | namespace _3Commas.BotCreator.MappingProfiles 6 | { 7 | public class BotSettingsProfile : Profile 8 | { 9 | public BotSettingsProfile() 10 | { 11 | CreateMap().ReverseMap(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Misc/AssemblyHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace _3Commas.BotCreator.Misc 4 | { 5 | public static class AssemblyHelper 6 | { 7 | public static string AssemblyTitle 8 | { 9 | get 10 | { 11 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 12 | if (attributes.Length > 0) 13 | { 14 | AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; 15 | if (titleAttribute.Title != "") 16 | { 17 | return titleAttribute.Title; 18 | } 19 | } 20 | return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); 21 | } 22 | } 23 | 24 | public static string AssemblyVersion 25 | { 26 | get 27 | { 28 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 29 | } 30 | } 31 | 32 | public static string AssemblyDescription 33 | { 34 | get 35 | { 36 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 37 | if (attributes.Length == 0) 38 | { 39 | return ""; 40 | } 41 | return ((AssemblyDescriptionAttribute)attributes[0]).Description; 42 | } 43 | } 44 | 45 | public static string AssemblyProduct 46 | { 47 | get 48 | { 49 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); 50 | if (attributes.Length == 0) 51 | { 52 | return ""; 53 | } 54 | return ((AssemblyProductAttribute)attributes[0]).Product; 55 | } 56 | } 57 | 58 | public static string AssemblyCopyright 59 | { 60 | get 61 | { 62 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 63 | if (attributes.Length == 0) 64 | { 65 | return ""; 66 | } 67 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 68 | } 69 | } 70 | 71 | public static string AssemblyCompany 72 | { 73 | get 74 | { 75 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 76 | if (attributes.Length == 0) 77 | { 78 | return ""; 79 | } 80 | return ((AssemblyCompanyAttribute)attributes[0]).Company; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Misc/BotManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using _3Commas.BotCreator._3CommasLayer; 7 | using _3Commas.BotCreator.ExchangeLayer; 8 | using _3Commas.BotCreator.ExchangeLayer.Entities; 9 | using _3Commas.BotCreator.Views.MainForm; 10 | using Microsoft.Extensions.Logging; 11 | using XCommas.Net.Objects; 12 | 13 | namespace _3Commas.BotCreator.Misc 14 | { 15 | public class BotManager 16 | { 17 | private readonly ILogger _logger; 18 | private readonly IXCommasClient _xCommasClient; 19 | private readonly IExchange _exchange; 20 | private static string[] Stablecoins => new[] { "AUD", "EURS", "EBASE", "GBP", "USDT", "USDC", "DAI", "TUSD", "BUSD", "PAX", "HUSD", "SUSD", "USDK", "MUSD", "GUSD", "SAI", "EOSDT", "USDS", "BITCNY", "TRYB", "RSV", "BGBP", "QC", "USNBT", "BKRW", "THKD" }; 21 | 22 | public BotManager(ILogger logger, IXCommasClient xCommasClient, IExchange exchange) 23 | { 24 | this._logger = logger; 25 | _xCommasClient = xCommasClient; 26 | _exchange = exchange; 27 | } 28 | 29 | public async Task> RetrieveAccounts() 30 | { 31 | var accounts = new List(); 32 | 33 | var response = await _xCommasClient.GetAccountsAsync(); 34 | _logger.LogInformation("Retrieving exchange information from 3Commas..."); 35 | if (!response.IsSuccess) 36 | { 37 | this._logger.LogError("Problem with 3Commas connection: " + response.Error); 38 | } 39 | else 40 | { 41 | _logger.LogInformation($"{response.Data.Length} Exchanges found"); 42 | accounts = response.Data.ToList(); 43 | } 44 | 45 | return accounts; 46 | } 47 | 48 | public async Task> RetrieveBlacklistedPairs() 49 | { 50 | var pairs = new List(); 51 | 52 | var response = await _xCommasClient.GetBotPairsBlackListAsync(); 53 | _logger.LogInformation("Retrieving pairs blacklist from 3Commas..."); 54 | if (!response.IsSuccess) 55 | { 56 | this._logger.LogError("Problem with 3Commas connection: " + response.Error); 57 | } 58 | else 59 | { 60 | _logger.LogInformation($"{response.Data.Pairs.Length} blacklisted pairs found"); 61 | pairs = response.Data.Pairs.ToList(); 62 | } 63 | 64 | return pairs; 65 | } 66 | 67 | public async Task PreviewBotsCreation(int numberOfBots, BotSettingViewModel settings, CancellationToken cancellationToken) 68 | { 69 | var existingBots = await GetAllBots(); 70 | var blacklistedPairs = await RetrieveBlacklistedPairs(); 71 | 72 | int created = 0; 73 | 74 | _logger.LogInformation($"Retrieving pairs from {_exchange.Name}..."); 75 | var prices = await _exchange.GetAllPairsByQuoteCurrency(settings.QuoteCurrency); 76 | _logger.LogInformation($"{prices.Count} Pairs for {settings.QuoteCurrency.ToUpper()} found"); 77 | 78 | foreach (var pair in prices.OrderByDescending(x => x.TotalTradedQuoteAssetVolume)) 79 | { 80 | if (cancellationToken.IsCancellationRequested) 81 | { 82 | _logger.LogInformation($"Operation cancelled!"); 83 | break; 84 | } 85 | 86 | var symbol = TransformPairTo3CommasSymbolString(pair.QuoteCurrency, pair.BaseCurrency); 87 | 88 | if (SymbolShouldBeSkipped(symbol, settings.SkipExistingPairs, settings.SkipBaseStablecoin, settings.Strategy, pair, existingBots, settings.SkipBlacklistedPairs, blacklistedPairs)) continue; 89 | 90 | var marketData = await _xCommasClient.GetCurrencyRateAsync(symbol); 91 | if (!marketData.IsSuccess) 92 | { 93 | if (!marketData.Error.StartsWith("Unknown pair")) 94 | { 95 | _logger.LogInformation($"Skipped Pair {symbol}. Reason: '{marketData.Error}'"); 96 | } 97 | continue; 98 | } 99 | 100 | var botName = NameHelper.GenerateBotName(settings.Botname, symbol, settings.Strategy); 101 | _logger.LogInformation($"Bot to be created: '{botName}'"); 102 | 103 | if (settings.BuyBaseCurrency && settings.BaseCurrencyToBuy > 0) 104 | { 105 | _logger.LogInformation($"Market Buy Order to be placed: {settings.BaseCurrencyToBuy} {pair.QuoteCurrency}"); 106 | } 107 | 108 | created++; 109 | 110 | if (created == numberOfBots) break; 111 | } 112 | 113 | _logger.LogInformation($"{created} bots to be created"); 114 | } 115 | 116 | public async Task CreateBots(int numberOfBots, bool enable, BotSettingViewModel settings, CancellationToken cancellationToken) 117 | { 118 | var existingBots = await GetAllBots(); 119 | var blacklistedPairs = await RetrieveBlacklistedPairs(); 120 | 121 | int created = 0; 122 | 123 | _logger.LogInformation($"Retrieving pairs from {_exchange.Name}..."); 124 | var prices = await _exchange.GetAllPairsByQuoteCurrency(settings.QuoteCurrency); 125 | _logger.LogInformation($"{prices.Count} Pairs for {settings.QuoteCurrency.ToUpper()} found"); 126 | 127 | foreach (var pair in prices.OrderByDescending(x => x.TotalTradedQuoteAssetVolume)) 128 | { 129 | if (cancellationToken.IsCancellationRequested) 130 | { 131 | _logger.LogInformation($"Operation cancelled!"); 132 | break; 133 | } 134 | 135 | var symbol = TransformPairTo3CommasSymbolString(pair.QuoteCurrency, pair.BaseCurrency); 136 | 137 | if (SymbolShouldBeSkipped(symbol, settings.SkipExistingPairs, settings.SkipBaseStablecoin, settings.Strategy, pair, existingBots, settings.SkipBlacklistedPairs, blacklistedPairs)) continue; 138 | 139 | var marketData = await _xCommasClient.GetCurrencyRateAsync(symbol); 140 | if (!marketData.IsSuccess) 141 | { 142 | if (!marketData.Error.StartsWith("Unknown pair")) 143 | { 144 | _logger.LogInformation($"Skipped Pair {symbol}. Reason: '{marketData.Error}'"); 145 | } 146 | continue; 147 | } 148 | 149 | var botName = NameHelper.GenerateBotName(settings.Botname, symbol, settings.Strategy); 150 | 151 | var bot = CreateBot(botName, enable, symbol, settings); 152 | var response = await _xCommasClient.CreateBotAsync(settings.ExchangeAccountId.Value, settings.Strategy, bot); 153 | if (!response.IsSuccess) 154 | { 155 | _logger.LogError($"Could not create bot for {symbol}: {response.Error.Replace(Environment.NewLine, " ")}"); 156 | continue; 157 | } 158 | 159 | if (enable) 160 | { 161 | var res = await _xCommasClient.EnableBotAsync(response.Data.Id); 162 | if (!res.IsSuccess) 163 | { 164 | _logger.LogError($"Bot '{botName}' created but there was an error with activation: {res.Error.Replace(Environment.NewLine, " ")}"); 165 | } 166 | else 167 | { 168 | _logger.LogInformation($"Bot created and started: '{botName}'"); 169 | } 170 | } 171 | else 172 | { 173 | _logger.LogInformation($"Bot created: '{botName}'"); 174 | } 175 | 176 | if (settings.BuyBaseCurrency && settings.BaseCurrencyToBuy > 0) 177 | { 178 | var placeOrderResult = await _exchange.PlaceOrder(pair, settings.BaseCurrencyToBuy); 179 | if (placeOrderResult.Success) 180 | { 181 | _logger.LogInformation(placeOrderResult.Message); 182 | } 183 | else 184 | { 185 | _logger.LogError(placeOrderResult.Message); 186 | } 187 | } 188 | 189 | created++; 190 | 191 | if (created == numberOfBots) break; 192 | } 193 | 194 | _logger.LogInformation($"{created} bots created"); 195 | } 196 | 197 | private bool SymbolShouldBeSkipped(string symbol, bool checkForExistingBots, bool checkForBaseStablecoin, Strategy strategy, Pair pair, List existingBots, bool checkForBlacklistedBots, List blacklistedPairs) 198 | { 199 | if (checkForBaseStablecoin && Stablecoins.Any(s => s.ToLower() == pair.BaseCurrency.ToLower())) 200 | { 201 | _logger.LogInformation($"Skipping {symbol} because base is a stablecoin"); 202 | return true; 203 | } 204 | 205 | if (checkForBlacklistedBots && blacklistedPairs.Contains(symbol)) 206 | { 207 | _logger.LogInformation($"Skipping {symbol} because it is blacklisted"); 208 | return true; 209 | } 210 | 211 | if (IsSymbolIgnored(symbol)) 212 | { 213 | _logger.LogInformation($"Skipping {symbol}"); 214 | return true; 215 | } 216 | 217 | if (checkForExistingBots && existingBots.Any(x => 218 | x.Pairs.Any(p => p.ToLower() == $"{pair.QuoteCurrency.ToLower()}_{pair.BaseCurrency.ToLower()}") && 219 | x.Strategy == strategy)) 220 | { 221 | _logger.LogInformation($"Bot for {strategy} {symbol} already exist"); 222 | return true; 223 | } 224 | 225 | return false; 226 | } 227 | 228 | private Bot CreateBot(string botName, bool enabled, string symbol, BotSettingViewModel request) 229 | { 230 | var bot = new Bot(); 231 | 232 | // Main Settings 233 | bot.Name = botName; 234 | bot.IsEnabled = enabled; 235 | bot.Type = ""; 236 | 237 | // Pairs 238 | bot.Pairs = new[] { symbol }; 239 | bot.MaxActiveDeals = 1; 240 | 241 | // Strategy 242 | bot.Strategy = request.Strategy; 243 | bot.ProfitCurrency = request.ProfitCurrency; 244 | bot.BaseOrderVolume = request.BaseOrderSize; 245 | bot.BaseOrderVolumeType = request.BaseOrderSizeType; 246 | bot.SafetyOrderVolume = request.SafetyOrderSize; 247 | bot.SafetyOrderVolumeType = request.SafetyOrderSizeType; 248 | bot.StartOrderType = request.StartOrderType; 249 | 250 | // Leverage 251 | bot.LeverageType = request.LeverageType; 252 | if (request.LeverageType != LeverageType.NotSpecified) 253 | { 254 | bot.LeverageCustomValue = request.LeverageCustomValue; 255 | } 256 | 257 | // Signals 258 | bot.Strategies = request.DealStartConditions; 259 | 260 | // Stop Loss 261 | decimal stopLossPercentage = 0; 262 | StopLossType stopLossType = StopLossType.StopLoss; 263 | bool stopLossTimeoutEnabled = false; 264 | int stopLossTimeoutInSeconds = 0; 265 | if (request.StopLossEnabled) 266 | { 267 | stopLossPercentage = request.StopLossPercentage; 268 | stopLossType = request.StopLossAction; 269 | stopLossTimeoutEnabled = request.StopLossTimeoutEnabled; 270 | stopLossTimeoutInSeconds = request.StopLossTimeout; 271 | } 272 | 273 | bot.StopLossPercentage = stopLossPercentage; 274 | bot.StopLossType = stopLossType; 275 | bot.StopLossTimeoutEnabled = stopLossTimeoutEnabled; 276 | bot.StopLossTimeoutInSeconds = stopLossTimeoutInSeconds; 277 | 278 | // TP 279 | bot.TakeProfitType = TakeProfitType.Total; 280 | bot.TakeProfit = request.TargetProfit; 281 | 282 | // Trailing 283 | bot.TrailingEnabled = request.TrailingEnabled; 284 | bot.TrailingDeviation = request.TrailingDeviation; 285 | 286 | // Safety orders 287 | bot.MaxSafetyOrders = request.MaxSafetyTradesCount; 288 | bot.ActiveSafetyOrdersCount = request.MaxActiveSafetyTradesCount; 289 | bot.SafetyOrderStepPercentage = request.PriceDeviationToOpenSafetyOrders; 290 | bot.MartingaleVolumeCoefficient = request.SafetyOrderVolumeScale; 291 | bot.MartingaleStepCoefficient = request.SafetyOrderStepScale; 292 | 293 | // Advanced 294 | bot.Cooldown = request.CooldownBetweenDeals; 295 | return bot; 296 | } 297 | 298 | private bool IsSymbolIgnored(string symbol) 299 | { 300 | return symbol.EndsWith("DOWN") 301 | || symbol.EndsWith("UP") 302 | || symbol.EndsWith("BULL") 303 | || symbol.EndsWith("BEAR"); 304 | } 305 | 306 | public static string TransformPairTo3CommasSymbolString(string quoteCurrency, string baseCurrency) 307 | { 308 | return $"{quoteCurrency.ToUpper()}_{baseCurrency}"; 309 | } 310 | 311 | private async Task> GetAllBots() 312 | { 313 | _logger.LogInformation("Retrieving existing Bots from 3commas..."); 314 | 315 | var bots = new List(); 316 | int take = 100; 317 | int skip = 0; 318 | while (true) 319 | { 320 | var result = await _xCommasClient.GetBotsAsync(limit: take, offset: skip); 321 | if (!result.IsSuccess) 322 | { 323 | throw new Exception("3Commas Connection Issue: " + result.Error); 324 | } 325 | if (result.Data.Length == 0) 326 | { 327 | break; 328 | } 329 | 330 | bots.AddRange(result.Data); 331 | skip += take; 332 | } 333 | 334 | _logger.LogInformation($"{bots.Count} Bots found"); 335 | return bots; 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Misc/ControlHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace _3Commas.BotCreator.Misc 5 | { 6 | public static class ControlHelper 7 | { 8 | public static void AddValuesToCombobox(ComboBox comboBox, TEnum defaultValue) 9 | { 10 | AddValuesToCombobox(comboBox); 11 | comboBox.Text = defaultValue.ToString(); 12 | } 13 | 14 | public static void AddValuesToCombobox(ComboBox comboBox) 15 | { 16 | comboBox.DataSource = (TEnum[])Enum.GetValues(typeof(TEnum)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Misc/NameHelper.cs: -------------------------------------------------------------------------------- 1 | using XCommas.Net.Objects; 2 | 3 | namespace _3Commas.BotCreator.Misc 4 | { 5 | public static class NameHelper 6 | { 7 | public static string GenerateBotName(string nameFormula, string symbol, Strategy strategy) 8 | { 9 | return nameFormula 10 | .Replace("{pair}", symbol) 11 | .Replace("{strategy}", strategy.ToString()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Misc/TextBoxLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace _3Commas.BotCreator.Misc 6 | { 7 | internal class TextBoxLogger : ILogger 8 | { 9 | private readonly TextBox _txtOutput; 10 | 11 | public TextBoxLogger(TextBox txtOutput) 12 | { 13 | this._txtOutput = txtOutput; 14 | } 15 | 16 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 17 | { 18 | string message = ""; 19 | if (formatter != null) 20 | { 21 | message += formatter(state, exception); 22 | } 23 | 24 | _txtOutput.AppendText($"{DateTime.Now} {logLevel}: {message}{Environment.NewLine}"); 25 | } 26 | 27 | public bool IsEnabled(LogLevel logLevel) 28 | { 29 | return true; 30 | } 31 | 32 | public IDisposable BeginScope(TState state) 33 | { 34 | return new NoopDisposable(); 35 | } 36 | 37 | private class NoopDisposable : IDisposable 38 | { 39 | public void Dispose() 40 | { 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Windows.Forms; 4 | using _3Commas.BotCreator.Infrastructure; 5 | using _3Commas.BotCreator.Services.BotSettingService; 6 | using _3Commas.BotCreator.Services.MessageBoxService; 7 | using _3Commas.BotCreator.Views.AboutBox; 8 | using _3Commas.BotCreator.Views.ChooseSignalView; 9 | using _3Commas.BotCreator.Views.MainForm; 10 | using _3Commas.BotCreator.Views.SaveTemplateView; 11 | using _3Commas.BotCreator.Views.SetApiKeyView; 12 | using AutoMapper; 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | namespace _3Commas.BotCreator 16 | { 17 | static class Program 18 | { 19 | /// 20 | /// The main entry point for the application. 21 | /// 22 | [STAThread] 23 | static void Main() 24 | { 25 | Application.EnableVisualStyles(); 26 | Application.SetCompatibleTextRenderingDefault(false); 27 | 28 | try 29 | { 30 | ConfigureServices(); 31 | EncryptConfigFile(); 32 | Application.Run((Form) ServiceProvider.GetRequiredService()); 33 | } 34 | catch (Exception e) 35 | { 36 | MessageBox.Show("Sorry, but something went wrong!" + Environment.NewLine + Environment.NewLine + 37 | "Please let me know that there was a problem and I will try to fit it for you. You can report this error here: " + 38 | "https://github.com/MarcDrexler/3Commas.BotCreator/issues" + Environment.NewLine + Environment.NewLine + 39 | "Error Details: " + Environment.NewLine + 40 | e.ToString(), "Sorry!", MessageBoxButtons.OK, MessageBoxIcon.Error); 41 | } 42 | } 43 | 44 | internal static IServiceProvider ServiceProvider { get; set; } 45 | 46 | static void ConfigureServices() 47 | { 48 | var services = new ServiceCollection(); 49 | services.AddSingleton(); 50 | services.AddTransient(); 51 | services.AddTransient(); 52 | services.AddTransient(); 53 | services.AddTransient(); 54 | services.AddSingleton(); 55 | services.AddTransient(); 56 | 57 | var config = new MapperConfiguration(cfg => cfg.AddMaps(typeof(Program))); 58 | services.AddSingleton(config.CreateMapper()); 59 | ServiceProvider = services.BuildServiceProvider(); 60 | } 61 | 62 | private static void EncryptConfigFile() 63 | { 64 | System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal); 65 | try 66 | { 67 | ConfigurationSection section = config.GetSection("userSettings/_3Commas.BotCreator.Properties.Settings"); 68 | if (!section.SectionInformation.IsProtected) 69 | { 70 | section.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider"); 71 | section.SectionInformation.ForceSave = true; 72 | config.Save(ConfigurationSaveMode.Full); 73 | } 74 | } 75 | catch (Exception e) 76 | { 77 | MessageBox.Show($"Could not encrypt config file at {config.FilePath}!{Environment.NewLine}{Environment.NewLine}The Error was: {e.Message} {Environment.NewLine}{Environment.NewLine}Your api key might be stored in cleartext. Make sure to remove it from the config file if you don't need it anymore.", "Notice", MessageBoxButtons.OK, MessageBoxIcon.Warning); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Bot Creator")] 8 | [assembly: AssemblyDescription("3Commas Bulk Bot Creation Tool.")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Bot Creator")] 12 | [assembly: AssemblyCopyright("Copyright © 2021")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("02d16940-5926-45ea-a706-21b8d3e1f643")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.18.0.0")] 35 | [assembly: AssemblyFileVersion("1.18.0.0")] 36 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/DataSources/_3Commas.BotCreator.Views.MainForm.BotSettings.datasource: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | _3Commas.BotCreator.Views.MainForm.BotSettings, BotCreator, Version=1.9.0.0, Culture=neutral, PublicKeyToken=null 10 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace _3Commas.BotCreator.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("_3Commas.BotCreator.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap _3Commas { 67 | get { 68 | object obj = ResourceManager.GetObject("_3Commas", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Bitmap. 75 | /// 76 | internal static System.Drawing.Bitmap Add_16x16 { 77 | get { 78 | object obj = ResourceManager.GetObject("Add_16x16", resourceCulture); 79 | return ((System.Drawing.Bitmap)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Bitmap. 85 | /// 86 | internal static System.Drawing.Bitmap Binance { 87 | get { 88 | object obj = ResourceManager.GetObject("Binance", resourceCulture); 89 | return ((System.Drawing.Bitmap)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Bitmap. 95 | /// 96 | internal static System.Drawing.Bitmap Cancel_16x16 { 97 | get { 98 | object obj = ResourceManager.GetObject("Cancel_16x16", resourceCulture); 99 | return ((System.Drawing.Bitmap)(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Drawing.Bitmap. 105 | /// 106 | internal static System.Drawing.Bitmap Clear_16x16 { 107 | get { 108 | object obj = ResourceManager.GetObject("Clear_16x16", resourceCulture); 109 | return ((System.Drawing.Bitmap)(obj)); 110 | } 111 | } 112 | 113 | /// 114 | /// Looks up a localized resource of type System.Drawing.Bitmap. 115 | /// 116 | internal static System.Drawing.Bitmap Close_16x16 { 117 | get { 118 | object obj = ResourceManager.GetObject("Close_16x16", resourceCulture); 119 | return ((System.Drawing.Bitmap)(obj)); 120 | } 121 | } 122 | 123 | /// 124 | /// Looks up a localized resource of type System.Drawing.Bitmap. 125 | /// 126 | internal static System.Drawing.Bitmap download { 127 | get { 128 | object obj = ResourceManager.GetObject("download", resourceCulture); 129 | return ((System.Drawing.Bitmap)(obj)); 130 | } 131 | } 132 | 133 | /// 134 | /// Looks up a localized resource of type System.Drawing.Bitmap. 135 | /// 136 | internal static System.Drawing.Bitmap Huobi { 137 | get { 138 | object obj = ResourceManager.GetObject("Huobi", resourceCulture); 139 | return ((System.Drawing.Bitmap)(obj)); 140 | } 141 | } 142 | 143 | /// 144 | /// Looks up a localized resource of type System.Drawing.Bitmap. 145 | /// 146 | internal static System.Drawing.Bitmap Info_16x16 { 147 | get { 148 | object obj = ResourceManager.GetObject("Info_16x16", resourceCulture); 149 | return ((System.Drawing.Bitmap)(obj)); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized resource of type System.Drawing.Bitmap. 155 | /// 156 | internal static System.Drawing.Bitmap Info_32x32 { 157 | get { 158 | object obj = ResourceManager.GetObject("Info_32x32", resourceCulture); 159 | return ((System.Drawing.Bitmap)(obj)); 160 | } 161 | } 162 | 163 | /// 164 | /// Looks up a localized resource of type System.Drawing.Bitmap. 165 | /// 166 | internal static System.Drawing.Bitmap Play_16x16 { 167 | get { 168 | object obj = ResourceManager.GetObject("Play_16x16", resourceCulture); 169 | return ((System.Drawing.Bitmap)(obj)); 170 | } 171 | } 172 | 173 | /// 174 | /// Looks up a localized resource of type System.Drawing.Bitmap. 175 | /// 176 | internal static System.Drawing.Bitmap Properties_32x32 { 177 | get { 178 | object obj = ResourceManager.GetObject("Properties_32x32", resourceCulture); 179 | return ((System.Drawing.Bitmap)(obj)); 180 | } 181 | } 182 | 183 | /// 184 | /// Looks up a localized resource of type System.Drawing.Bitmap. 185 | /// 186 | internal static System.Drawing.Bitmap Save_16x16 { 187 | get { 188 | object obj = ResourceManager.GetObject("Save_16x16", resourceCulture); 189 | return ((System.Drawing.Bitmap)(obj)); 190 | } 191 | } 192 | 193 | /// 194 | /// Looks up a localized resource of type System.Drawing.Bitmap. 195 | /// 196 | internal static System.Drawing.Bitmap ShowTestReport_16x16 { 197 | get { 198 | object obj = ResourceManager.GetObject("ShowTestReport_16x16", resourceCulture); 199 | return ((System.Drawing.Bitmap)(obj)); 200 | } 201 | } 202 | 203 | /// 204 | /// Looks up a localized resource of type System.Drawing.Bitmap. 205 | /// 206 | internal static System.Drawing.Bitmap Suggestion_16x16 { 207 | get { 208 | object obj = ResourceManager.GetObject("Suggestion_16x16", resourceCulture); 209 | return ((System.Drawing.Bitmap)(obj)); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\Info_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | ..\Resources\Save_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | ..\resources\huobi.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | ..\resources\binance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | 134 | ..\Resources\Suggestion_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | 137 | ..\Resources\ShowTestReport_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 138 | 139 | 140 | ..\Resources\Play_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 141 | 142 | 143 | ..\Resources\Add_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 144 | 145 | 146 | ..\Resources\Properties_32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 147 | 148 | 149 | ..\Resources\Info_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 150 | 151 | 152 | ..\Resources\Cancel_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 153 | 154 | 155 | ..\resources\3commas.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 156 | 157 | 158 | ..\Resources\download.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 159 | 160 | 161 | ..\Resources\Clear_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 162 | 163 | 164 | ..\Resources\Close_16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 165 | 166 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace _3Commas.BotCreator.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("")] 29 | public string ApiKey3Commas { 30 | get { 31 | return ((string)(this["ApiKey3Commas"])); 32 | } 33 | set { 34 | this["ApiKey3Commas"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("")] 41 | public string ApiKeyBinance { 42 | get { 43 | return ((string)(this["ApiKeyBinance"])); 44 | } 45 | set { 46 | this["ApiKeyBinance"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("")] 53 | public string ApiKeyHuobi { 54 | get { 55 | return ((string)(this["ApiKeyHuobi"])); 56 | } 57 | set { 58 | this["ApiKeyHuobi"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("")] 65 | public string Secret3Commas { 66 | get { 67 | return ((string)(this["Secret3Commas"])); 68 | } 69 | set { 70 | this["Secret3Commas"] = value; 71 | } 72 | } 73 | 74 | [global::System.Configuration.UserScopedSettingAttribute()] 75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 76 | [global::System.Configuration.DefaultSettingValueAttribute("")] 77 | public string SecretBinance { 78 | get { 79 | return ((string)(this["SecretBinance"])); 80 | } 81 | set { 82 | this["SecretBinance"] = value; 83 | } 84 | } 85 | 86 | [global::System.Configuration.UserScopedSettingAttribute()] 87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 88 | [global::System.Configuration.DefaultSettingValueAttribute("")] 89 | public string SecretHuobi { 90 | get { 91 | return ((string)(this["SecretHuobi"])); 92 | } 93 | set { 94 | this["SecretHuobi"] = value; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/3Commas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/3Commas.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Add_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Add_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Binance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Binance.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Cancel_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Cancel_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Clear_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Clear_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Close_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Close_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Huobi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Huobi.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Info_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Info_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Info_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Info_32x32.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Play_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Play_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Properties_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Properties_32x32.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Save_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Save_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/ShowTestReport_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/ShowTestReport_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/Suggestion_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/Suggestion_16x16.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Resources/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/Resources/download.png -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Services/BotSettingService/BotSettingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using _3Commas.BotCreator.Services.MessageBoxService; 6 | using Newtonsoft.Json; 7 | 8 | namespace _3Commas.BotCreator.Services.BotSettingService 9 | { 10 | public class BotSettingService : IBotSettingService 11 | { 12 | private readonly IMessageBoxService _mbs; 13 | private readonly List _settings; 14 | private readonly string _filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "BotCreator", "botsettings.json"); 15 | 16 | public BotSettingService(IMessageBoxService mbs) 17 | { 18 | _mbs = mbs; 19 | var directory = Path.GetDirectoryName(_filePath); 20 | if (!Directory.Exists(directory)) 21 | { 22 | Directory.CreateDirectory(directory); 23 | } 24 | if (File.Exists(_filePath)) 25 | { 26 | var json = File.ReadAllText(_filePath); 27 | _settings = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings 28 | { 29 | TypeNameHandling = TypeNameHandling.All 30 | }); 31 | } 32 | else 33 | { 34 | _settings = new List(); 35 | } 36 | } 37 | 38 | public List GetAll() 39 | { 40 | return _settings.OrderByDescending(x => x.UpdatedAt).ToList(); 41 | } 42 | 43 | public BotSettings GetByName(string name) 44 | { 45 | return _settings.Single(x => x.Name == name); 46 | } 47 | 48 | public bool Insert(BotSettings settings, string name) 49 | { 50 | try 51 | { 52 | settings.Id = Guid.NewGuid(); 53 | settings.Name = name; 54 | settings.UpdatedAt = DateTime.Now; 55 | _settings.Add(settings); 56 | Save(); 57 | return true; 58 | } 59 | catch (Exception e) 60 | { 61 | _mbs.ShowError(e.ToString()); 62 | return false; 63 | } 64 | } 65 | 66 | public bool Update(BotSettings settings, Guid id) 67 | { 68 | try 69 | { 70 | var oldSettings = _settings.Single(x => x.Id == id); 71 | _settings.Remove(oldSettings); 72 | settings.Id = Guid.NewGuid(); 73 | settings.UpdatedAt = DateTime.Now; 74 | settings.Name = oldSettings.Name; 75 | _settings.Add(settings); 76 | Save(); 77 | return true; 78 | } 79 | catch (Exception e) 80 | { 81 | _mbs.ShowError(e.ToString()); 82 | return false; 83 | } 84 | } 85 | 86 | public bool Delete(Guid id) 87 | { 88 | try 89 | { 90 | _settings.RemoveAll(s => s.Id == id); 91 | Save(); 92 | return true; 93 | } 94 | catch (Exception e) 95 | { 96 | _mbs.ShowError(e.ToString()); 97 | return false; 98 | } 99 | } 100 | 101 | private void Save() 102 | { 103 | File.WriteAllText(_filePath, JsonConvert.SerializeObject(_settings, Formatting.Indented, new JsonSerializerSettings 104 | { 105 | TypeNameHandling = TypeNameHandling.All 106 | })); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Services/BotSettingService/BotSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XCommas.Net.Objects; 4 | 5 | namespace _3Commas.BotCreator.Services.BotSettingService 6 | { 7 | public class BotSettings 8 | { 9 | public Guid Id { get; set; } 10 | 11 | public DateTime UpdatedAt { get; set; } 12 | 13 | public string Name { get; set; } 14 | 15 | public bool SkipExistingPairs { get; set; } 16 | 17 | public bool SkipBlacklistedPairs { get; set; } 18 | 19 | public bool SkipBaseStablecoin { get; set; } 20 | 21 | public string QuoteCurrency { get; set; } 22 | 23 | public string Botname { get; set; } 24 | 25 | public int? ExchangeAccountId { get; set; } 26 | 27 | public string Strategy { get; set; } 28 | 29 | public string ProfitCurrency { get; set; } 30 | 31 | public decimal BaseOrderSize { get; set; } 32 | 33 | public string BaseOrderSizeType { get; set; } 34 | 35 | public decimal SafetyOrderSize { get; set; } 36 | 37 | public string SafetyOrderSizeType { get; set; } 38 | 39 | public string StartOrderType { get; set; } 40 | 41 | public decimal TargetProfit { get; set; } 42 | 43 | public string TargetProfitType { get; set; } 44 | 45 | public bool TrailingEnabled { get; set; } 46 | 47 | public decimal TrailingDeviation { get; set; } 48 | 49 | public List DealStartConditions { get; set; } = new List(); 50 | 51 | public bool StopLossEnabled { get; set; } 52 | 53 | public decimal StopLossPercentage { get; set; } 54 | 55 | public string StopLossAction { get; set; } 56 | 57 | public bool StopLossTimeoutEnabled { get; set; } 58 | 59 | public int StopLossTimeout { get; set; } 60 | 61 | public int MaxSafetyTradesCount { get; set; } 62 | 63 | public int MaxActiveSafetyTradesCount { get; set; } 64 | 65 | public decimal PriceDeviationToOpenSafetyOrders { get; set; } 66 | 67 | public decimal SafetyOrderVolumeScale { get; set; } 68 | 69 | public decimal SafetyOrderStepScale { get; set; } 70 | 71 | public decimal DontStartDealIfDailyVolumeIsLessThan { get; set; } 72 | 73 | public decimal? MinimumPriceToOpenDeal { get; set; } 74 | 75 | public decimal? MaxPriceToOpenDeal { get; set; } 76 | 77 | public int CooldownBetweenDeals { get; set; } 78 | 79 | public int? OpenDealsAndStopCount { get; set; } 80 | 81 | public string LeverageType { get; set; } 82 | 83 | public int LeverageCustomValue { get; set; } 84 | 85 | public bool BuyBaseCurrency { get; set; } 86 | 87 | public decimal BaseCurrencyToBuy { get; set; } 88 | } 89 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Services/BotSettingService/IBotSettingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace _3Commas.BotCreator.Services.BotSettingService 5 | { 6 | public interface IBotSettingService 7 | { 8 | List GetAll(); 9 | 10 | BotSettings GetByName(string name); 11 | 12 | bool Insert(BotSettings settings, string name); 13 | 14 | bool Update(BotSettings settings, Guid id); 15 | 16 | bool Delete(Guid id); 17 | } 18 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Services/MessageBoxService/IMessageBoxService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace _3Commas.BotCreator.Services.MessageBoxService 4 | { 5 | public interface IMessageBoxService 6 | { 7 | DialogResult ShowError(string text, string title = "Error"); 8 | DialogResult ShowQuestion(string text); 9 | DialogResult ShowInformation(string text); 10 | } 11 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Services/MessageBoxService/MessageBoxService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace _3Commas.BotCreator.Services.MessageBoxService 4 | { 5 | public class MessageBoxService : IMessageBoxService 6 | { 7 | public DialogResult ShowError(string text, string title = "Error") 8 | { 9 | return MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Error); 10 | } 11 | 12 | public DialogResult ShowQuestion(string text) 13 | { 14 | return MessageBox.Show(text, "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 15 | } 16 | 17 | public DialogResult ShowInformation(string text) 18 | { 19 | return MessageBox.Show(text, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/AboutBox/AboutBox.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator.Views.AboutBox 2 | { 3 | partial class AboutBox 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | protected override void Dispose(bool disposing) 14 | { 15 | if (disposing && (components != null)) 16 | { 17 | components.Dispose(); 18 | } 19 | base.Dispose(disposing); 20 | } 21 | 22 | #region Windows Form Designer generated code 23 | 24 | /// 25 | /// Required method for Designer support - do not modify 26 | /// the contents of this method with the code editor. 27 | /// 28 | private void InitializeComponent() 29 | { 30 | this.labelProductName = new System.Windows.Forms.Label(); 31 | this.labelVersion = new System.Windows.Forms.Label(); 32 | this.textBoxDescription = new System.Windows.Forms.TextBox(); 33 | this.okButton = new System.Windows.Forms.Button(); 34 | this.pbBuyMeACoffee = new System.Windows.Forms.PictureBox(); 35 | this.linkGithubProject = new System.Windows.Forms.LinkLabel(); 36 | this.label1 = new System.Windows.Forms.Label(); 37 | ((System.ComponentModel.ISupportInitialize)(this.pbBuyMeACoffee)).BeginInit(); 38 | this.SuspendLayout(); 39 | // 40 | // labelProductName 41 | // 42 | this.labelProductName.Location = new System.Drawing.Point(15, 26); 43 | this.labelProductName.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 44 | this.labelProductName.MaximumSize = new System.Drawing.Size(408, 17); 45 | this.labelProductName.Name = "labelProductName"; 46 | this.labelProductName.Size = new System.Drawing.Size(408, 17); 47 | this.labelProductName.TabIndex = 19; 48 | this.labelProductName.Text = "Product Name"; 49 | this.labelProductName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 50 | // 51 | // labelVersion 52 | // 53 | this.labelVersion.Location = new System.Drawing.Point(15, 49); 54 | this.labelVersion.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 55 | this.labelVersion.MaximumSize = new System.Drawing.Size(408, 17); 56 | this.labelVersion.Name = "labelVersion"; 57 | this.labelVersion.Size = new System.Drawing.Size(408, 17); 58 | this.labelVersion.TabIndex = 0; 59 | this.labelVersion.Text = "Version"; 60 | this.labelVersion.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 61 | // 62 | // textBoxDescription 63 | // 64 | this.textBoxDescription.Location = new System.Drawing.Point(15, 84); 65 | this.textBoxDescription.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); 66 | this.textBoxDescription.Multiline = true; 67 | this.textBoxDescription.Name = "textBoxDescription"; 68 | this.textBoxDescription.ReadOnly = true; 69 | this.textBoxDescription.ScrollBars = System.Windows.Forms.ScrollBars.Both; 70 | this.textBoxDescription.Size = new System.Drawing.Size(408, 60); 71 | this.textBoxDescription.TabIndex = 23; 72 | this.textBoxDescription.TabStop = false; 73 | this.textBoxDescription.Text = "Description"; 74 | // 75 | // okButton 76 | // 77 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 78 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 79 | this.okButton.Location = new System.Drawing.Point(348, 249); 80 | this.okButton.Name = "okButton"; 81 | this.okButton.Size = new System.Drawing.Size(75, 23); 82 | this.okButton.TabIndex = 24; 83 | this.okButton.Text = "&OK"; 84 | // 85 | // pbBuyMeACoffee 86 | // 87 | this.pbBuyMeACoffee.Cursor = System.Windows.Forms.Cursors.Hand; 88 | this.pbBuyMeACoffee.Image = global::_3Commas.BotCreator.Properties.Resources.download; 89 | this.pbBuyMeACoffee.Location = new System.Drawing.Point(15, 231); 90 | this.pbBuyMeACoffee.Name = "pbBuyMeACoffee"; 91 | this.pbBuyMeACoffee.Size = new System.Drawing.Size(165, 41); 92 | this.pbBuyMeACoffee.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; 93 | this.pbBuyMeACoffee.TabIndex = 47; 94 | this.pbBuyMeACoffee.TabStop = false; 95 | this.pbBuyMeACoffee.Click += new System.EventHandler(this.pbBuyMeACoffee_Click); 96 | // 97 | // linkGithubProject 98 | // 99 | this.linkGithubProject.AutoSize = true; 100 | this.linkGithubProject.Location = new System.Drawing.Point(390, 154); 101 | this.linkGithubProject.Name = "linkGithubProject"; 102 | this.linkGithubProject.Size = new System.Drawing.Size(28, 13); 103 | this.linkGithubProject.TabIndex = 50; 104 | this.linkGithubProject.TabStop = true; 105 | this.linkGithubProject.Text = "here"; 106 | this.linkGithubProject.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkGithubProject_LinkClicked); 107 | // 108 | // label1 109 | // 110 | this.label1.AutoSize = true; 111 | this.label1.Location = new System.Drawing.Point(23, 154); 112 | this.label1.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 113 | this.label1.MaximumSize = new System.Drawing.Size(408, 17); 114 | this.label1.Name = "label1"; 115 | this.label1.Size = new System.Drawing.Size(370, 13); 116 | this.label1.TabIndex = 51; 117 | this.label1.Text = "If you think something is broken or have any questions, please open an Issue"; 118 | this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 119 | // 120 | // AboutBox 121 | // 122 | this.AcceptButton = this.okButton; 123 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 124 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 125 | this.ClientSize = new System.Drawing.Size(435, 284); 126 | this.Controls.Add(this.linkGithubProject); 127 | this.Controls.Add(this.label1); 128 | this.Controls.Add(this.labelProductName); 129 | this.Controls.Add(this.labelVersion); 130 | this.Controls.Add(this.okButton); 131 | this.Controls.Add(this.pbBuyMeACoffee); 132 | this.Controls.Add(this.textBoxDescription); 133 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 134 | this.MaximizeBox = false; 135 | this.MinimizeBox = false; 136 | this.Name = "AboutBox"; 137 | this.Padding = new System.Windows.Forms.Padding(9); 138 | this.ShowIcon = false; 139 | this.ShowInTaskbar = false; 140 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 141 | this.Text = "About"; 142 | ((System.ComponentModel.ISupportInitialize)(this.pbBuyMeACoffee)).EndInit(); 143 | this.ResumeLayout(false); 144 | this.PerformLayout(); 145 | 146 | } 147 | 148 | #endregion 149 | private System.Windows.Forms.Label labelProductName; 150 | private System.Windows.Forms.Label labelVersion; 151 | private System.Windows.Forms.TextBox textBoxDescription; 152 | private System.Windows.Forms.Button okButton; 153 | private System.Windows.Forms.PictureBox pbBuyMeACoffee; 154 | private System.Windows.Forms.LinkLabel linkGithubProject; 155 | private System.Windows.Forms.Label label1; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/AboutBox/AboutBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Windows.Forms; 5 | 6 | namespace _3Commas.BotCreator.Views.AboutBox 7 | { 8 | partial class AboutBox : Form 9 | { 10 | public AboutBox() 11 | { 12 | InitializeComponent(); 13 | this.Text = String.Format("About {0}", AssemblyTitle); 14 | this.labelProductName.Text = AssemblyProduct; 15 | this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); 16 | this.textBoxDescription.Text = AssemblyDescription; 17 | } 18 | 19 | #region Assembly Attribute Accessors 20 | 21 | public string AssemblyTitle 22 | { 23 | get 24 | { 25 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 26 | if (attributes.Length > 0) 27 | { 28 | AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; 29 | if (titleAttribute.Title != "") 30 | { 31 | return titleAttribute.Title; 32 | } 33 | } 34 | return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); 35 | } 36 | } 37 | 38 | public string AssemblyVersion 39 | { 40 | get 41 | { 42 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 43 | } 44 | } 45 | 46 | public string AssemblyDescription 47 | { 48 | get 49 | { 50 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 51 | if (attributes.Length == 0) 52 | { 53 | return ""; 54 | } 55 | return ((AssemblyDescriptionAttribute)attributes[0]).Description; 56 | } 57 | } 58 | 59 | public string AssemblyProduct 60 | { 61 | get 62 | { 63 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); 64 | if (attributes.Length == 0) 65 | { 66 | return ""; 67 | } 68 | return ((AssemblyProductAttribute)attributes[0]).Product; 69 | } 70 | } 71 | 72 | public string AssemblyCopyright 73 | { 74 | get 75 | { 76 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 77 | if (attributes.Length == 0) 78 | { 79 | return ""; 80 | } 81 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 82 | } 83 | } 84 | 85 | public string AssemblyCompany 86 | { 87 | get 88 | { 89 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 90 | if (attributes.Length == 0) 91 | { 92 | return ""; 93 | } 94 | return ((AssemblyCompanyAttribute)attributes[0]).Company; 95 | } 96 | } 97 | #endregion 98 | 99 | private void pbBuyMeACoffee_Click(object sender, EventArgs e) 100 | { 101 | Process.Start("https://www.buymeacoffee.com/marcdrexler"); 102 | } 103 | 104 | private void linkGithubProject_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 105 | { 106 | Process.Start("https://github.com/MarcDrexler/3Commas.BotCreator/issues/new"); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/AboutBox/AboutBox.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/ChooseSignalView/ChooseSignalView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using _3Commas.BotCreator.Misc; 4 | using XCommas.Net.Objects; 5 | 6 | namespace _3Commas.BotCreator.Views.ChooseSignalView 7 | { 8 | public partial class ChooseSignalView : Form 9 | { 10 | private BotStrategy _strategy; 11 | 12 | public ChooseSignalView() 13 | { 14 | InitializeComponent(); 15 | 16 | ControlHelper.AddValuesToCombobox(cmbTradingViewTime); 17 | ControlHelper.AddValuesToCombobox(cmbTradingViewType); 18 | ControlHelper.AddValuesToCombobox(cmbRsiTime); 19 | ControlHelper.AddValuesToCombobox(cmbUltTime); 20 | ControlHelper.AddValuesToCombobox(cmbTaPresetsTime); 21 | ControlHelper.AddValuesToCombobox(cmbTaPresetsType); 22 | } 23 | 24 | public BotStrategy Strategy => _strategy; 25 | 26 | private void radioButton_CheckedChanged(object sender, EventArgs e) 27 | { 28 | panelRsi.Visible = (sender == radioButtonRsi); 29 | panelUlt.Visible = (sender == radioButtonUlt); 30 | panelTaPresets.Visible = (sender == radioButtonTaPresets); 31 | panelTradingView.Visible = (sender == radioButtonTradingView); 32 | txtCustom.Visible = (sender == radioButtonCustom); 33 | } 34 | 35 | private void okButton_Click(object sender, EventArgs e) 36 | { 37 | if (radioButtonRsi.Checked) 38 | { 39 | if (cmbRsiTime.SelectedItem == null) 40 | { 41 | MessageBox.Show("Time is missing"); 42 | return; 43 | } 44 | 45 | IndicatorTime.TryParse(cmbRsiTime.SelectedItem.ToString(), out IndicatorTime time); 46 | _strategy = new RsiBotStrategy { Options = new RsiOptions(time, (int)numRsiPoints.Value) }; 47 | } 48 | 49 | if (radioButtonUlt.Checked) 50 | { 51 | if (cmbUltTime.SelectedItem == null) 52 | { 53 | MessageBox.Show("Time is missing"); 54 | return; 55 | } 56 | 57 | IndicatorTime.TryParse(cmbUltTime.SelectedItem.ToString(), out IndicatorTime time); 58 | _strategy = new UltBotStrategy { Options = new UltOptions(time, (int)numUltPoints.Value) }; 59 | } 60 | 61 | if (radioButtonTradingView.Checked) 62 | { 63 | if (cmbTradingViewTime.SelectedItem == null) 64 | { 65 | MessageBox.Show("Time is missing"); 66 | return; 67 | } 68 | if (cmbTradingViewType.SelectedItem == null) 69 | { 70 | MessageBox.Show("Type is missing"); 71 | return; 72 | } 73 | 74 | TradingViewTime.TryParse(cmbTradingViewTime.SelectedItem.ToString(), out TradingViewTime time); 75 | TradingViewIndicatorType.TryParse(cmbTradingViewType.SelectedItem.ToString(), out TradingViewIndicatorType type); 76 | _strategy = new TradingViewBotStrategy { Options = new TradingViewOptions(type, time) }; 77 | } 78 | 79 | if (radioButtonTaPresets.Checked) 80 | { 81 | if (cmbTaPresetsTime.SelectedItem == null) 82 | { 83 | MessageBox.Show("Time is missing"); 84 | return; 85 | } 86 | if (cmbTaPresetsType.SelectedItem == null) 87 | { 88 | MessageBox.Show("Type is missing"); 89 | return; 90 | } 91 | 92 | IndicatorTime.TryParse(cmbTaPresetsTime.SelectedItem.ToString(), out IndicatorTime time); 93 | TaPresetsType.TryParse(cmbTaPresetsType.SelectedItem.ToString(), out TaPresetsType type); 94 | _strategy = new TaPresetsBotStrategy { Options = new TaPresetsOptions(type, time) }; 95 | } 96 | 97 | if (radioButtonManual.Checked) _strategy = new ManualStrategy(); 98 | if (radioButtonNonstop.Checked) _strategy = new NonStopBotStrategy(); 99 | 100 | if (radioButtonCustom.Checked) 101 | { 102 | if (string.IsNullOrWhiteSpace(txtCustom.Text)) 103 | { 104 | MessageBox.Show("Name is missing"); 105 | return; 106 | } 107 | _strategy = new UnknownStrategy(txtCustom.Text); 108 | } 109 | 110 | this.DialogResult = DialogResult.OK; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/ChooseSignalView/ChooseSignalView.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/MainForm/BotSettingViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XCommas.Net.Objects; 4 | 5 | namespace _3Commas.BotCreator.Views.MainForm 6 | { 7 | public class BotSettingViewModel 8 | { 9 | public BotSettingViewModel() 10 | { 11 | // Default Values 12 | SkipExistingPairs = true; 13 | SkipBaseStablecoin = true; 14 | SkipBlacklistedPairs = true; 15 | QuoteCurrency = "USDT"; 16 | Botname = "{strategy} {pair} Bot"; 17 | BaseOrderSize = 10; 18 | SafetyOrderSize = 11; 19 | TargetProfit = 1; 20 | MaxSafetyTradesCount = 1; 21 | MaxActiveSafetyTradesCount = 1; 22 | PriceDeviationToOpenSafetyOrders = 1; 23 | SafetyOrderVolumeScale = 1; 24 | SafetyOrderStepScale = 1; 25 | CooldownBetweenDeals = 0; 26 | StartOrderType = StartOrderType.Limit; 27 | Strategy = Strategy.Long; 28 | StopLossAction = StopLossType.StopLoss; 29 | StopLossPercentage = (decimal) 1; 30 | StopLossTimeout = 1; 31 | LeverageType = LeverageType.NotSpecified; 32 | LeverageCustomValue = 1; 33 | ProfitCurrency = ProfitCurrency.QuoteCurrency; 34 | BaseOrderSizeType = VolumeType.QuoteCurrency; 35 | SafetyOrderSizeType = VolumeType.QuoteCurrency; 36 | } 37 | 38 | public BotSettingViewModel(LeverageType leverageType, int customLeverageValue, decimal stopLossPercentage, StopLossType stopLossType, bool stopLossTimeoutEnabled, int stopLossTimeoutInSeconds, bool checkForExistingBots, bool checkForBlacklistedPairs, bool checkForBaseStablecoin, string quoteCurrency, Strategy strategy, StartOrderType startOrderType, int maxSafetyOrders, int activeSafetyOrdersCount, decimal safetyOrderStepPercentage, decimal martingaleVolumeCoefficient, decimal martingaleStepCoefficient, decimal takeProfitPercentage, bool trailingEnabled, decimal trailingDeviation, string nameFormula, decimal baseOrderVolume, decimal safetyOrderVolume, List dealStartConditions, int cooldownBetweenDeals, int accountId, decimal amountToBuyInQuoteCurrency) 39 | { 40 | LeverageType = leverageType; 41 | LeverageCustomValue = customLeverageValue; 42 | StopLossPercentage = stopLossPercentage; 43 | StopLossAction = stopLossType; 44 | StopLossTimeoutEnabled = stopLossTimeoutEnabled; 45 | StopLossTimeout = stopLossTimeoutInSeconds; 46 | SkipExistingPairs = checkForExistingBots; 47 | QuoteCurrency = quoteCurrency; 48 | Strategy = strategy; 49 | StartOrderType = startOrderType; 50 | MaxSafetyTradesCount = maxSafetyOrders; 51 | MaxActiveSafetyTradesCount = activeSafetyOrdersCount; 52 | PriceDeviationToOpenSafetyOrders = safetyOrderStepPercentage; 53 | SafetyOrderVolumeScale = martingaleVolumeCoefficient; 54 | SafetyOrderStepScale = martingaleStepCoefficient; 55 | TargetProfit = takeProfitPercentage; 56 | TrailingEnabled = trailingEnabled; 57 | TrailingDeviation = trailingDeviation; 58 | Botname = nameFormula; 59 | BaseOrderSize = baseOrderVolume; 60 | SafetyOrderSize = safetyOrderVolume; 61 | DealStartConditions = dealStartConditions; 62 | CooldownBetweenDeals = cooldownBetweenDeals; 63 | ExchangeAccountId = accountId; 64 | BaseCurrencyToBuy = amountToBuyInQuoteCurrency; 65 | SkipBlacklistedPairs = checkForBlacklistedPairs; 66 | SkipBaseStablecoin = checkForBaseStablecoin; 67 | } 68 | 69 | public string Name { get; set; } 70 | 71 | public LeverageType LeverageType { get; set; } 72 | 73 | public int LeverageCustomValue { get; set; } 74 | 75 | public bool SkipExistingPairs { get; set; } 76 | 77 | public bool SkipBlacklistedPairs { get; set; } 78 | 79 | public bool SkipBaseStablecoin { get; set; } 80 | 81 | public string QuoteCurrency { get; set; } 82 | 83 | public string Botname { get; set; } 84 | 85 | public int? ExchangeAccountId { get; set; } 86 | 87 | public Strategy Strategy { get; set; } 88 | 89 | public ProfitCurrency ProfitCurrency { get; set; } 90 | 91 | public decimal BaseOrderSize { get; set; } 92 | 93 | public VolumeType BaseOrderSizeType { get; set; } 94 | 95 | public decimal SafetyOrderSize { get; set; } 96 | 97 | public VolumeType SafetyOrderSizeType { get; set; } 98 | 99 | public StartOrderType StartOrderType { get; set; } 100 | 101 | public decimal TargetProfit { get; set; } 102 | 103 | public string TargetProfitType { get; set; } 104 | 105 | public bool TrailingEnabled { get; set; } 106 | 107 | public decimal TrailingDeviation { get; set; } 108 | 109 | public List DealStartConditions { get; set; } = new List(); 110 | 111 | public bool StopLossEnabled { get; set; } 112 | 113 | public decimal StopLossPercentage { get; set; } 114 | 115 | public StopLossType StopLossAction { get; set; } 116 | 117 | public bool StopLossTimeoutEnabled { get; set; } 118 | 119 | public int StopLossTimeout { get; set; } 120 | 121 | public int MaxSafetyTradesCount { get; set; } 122 | 123 | public int MaxActiveSafetyTradesCount { get; set; } 124 | 125 | public decimal PriceDeviationToOpenSafetyOrders { get; set; } 126 | 127 | public decimal SafetyOrderVolumeScale { get; set; } 128 | 129 | public decimal SafetyOrderStepScale { get; set; } 130 | 131 | public decimal DontStartDealIfDailyVolumeIsLessThan { get; set; } 132 | 133 | public decimal? MinimumPriceToOpenDeal { get; set; } 134 | 135 | public decimal? MaxPriceToOpenDeal { get; set; } 136 | 137 | public int CooldownBetweenDeals { get; set; } 138 | 139 | public int? OpenDealsAndStopCount { get; set; } 140 | 141 | public bool BuyBaseCurrency { get; set; } 142 | 143 | public decimal BaseCurrencyToBuy { get; set; } 144 | } 145 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/MainForm/IMainForm.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using _3Commas.BotCreator.Infrastructure; 3 | using _3Commas.BotCreator.Services.BotSettingService; 4 | using XCommas.Net.Objects; 5 | 6 | namespace _3Commas.BotCreator.Views.MainForm 7 | { 8 | public interface IMainForm : IViewBase 9 | { 10 | void BindCombos(); 11 | bool IsBinanceSelected { get; } 12 | bool IsHuobiSelected { get; } 13 | int StartConditionsCount { get; } 14 | int NumberOfBotsToCreate { get; } 15 | bool Enable { get; } 16 | List SelectedStartConditions { get;} 17 | BotSettings SelectedTemplate { get; } 18 | bool UsePaperTrading { get; } 19 | void SetNamePreview(string name); 20 | void BindAccountsAndSetSelection(List accounts, int? account); 21 | void ClearLog(); 22 | void RefreshQuoteCurrencyLabel(string text); 23 | void SetControlStateToBuyBaseCurrency(bool isEnabled); 24 | void SetCreateInProgress(bool inProgress); 25 | void RefreshStartConditions(List startConditions); 26 | void RefreshTemplateCombo(List templates, bool selectFirst); 27 | void BindSetting(BotSettingViewModel botSetting); 28 | } 29 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/MainForm/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | using _3Commas.BotCreator.Misc; 6 | using _3Commas.BotCreator.Services.BotSettingService; 7 | using _3Commas.BotCreator.Services.MessageBoxService; 8 | using AutoMapper; 9 | using XCommas.Net.Objects; 10 | 11 | namespace _3Commas.BotCreator.Views.MainForm 12 | { 13 | public partial class MainForm : Form, IMainForm 14 | { 15 | private readonly MainFormPresenter _presenter; 16 | 17 | public MainForm(IMessageBoxService mbs, IBotSettingService bss, IMapper mapper) 18 | { 19 | InitializeComponent(); 20 | 21 | this.Text = $"{AssemblyHelper.AssemblyTitle} {AssemblyHelper.AssemblyVersion}"; 22 | 23 | _presenter = new MainFormPresenter(this, new TextBoxLogger(txtOutput), mbs, bss, mapper); 24 | 25 | panelStopLoss.DataBindings.Add(nameof(Enabled), chkStopLossEnabled, nameof(CheckBox.Checked)); 26 | lblStopLossTimeoutUnit.DataBindings.Add(nameof(Enabled), chkStopLossTimeoutEnabled, nameof(CheckBox.Checked)); 27 | 28 | 29 | } 30 | 31 | private async void MainForm_Load(object sender, EventArgs e) 32 | { 33 | await _presenter.OnViewReady(); 34 | } 35 | 36 | public void BindCombos() 37 | { 38 | ControlHelper.AddValuesToCombobox(cmbStrategy, Strategy.Long); 39 | ControlHelper.AddValuesToCombobox(cmbStartOrderType); 40 | ControlHelper.AddValuesToCombobox(cmbStopLossType); 41 | ControlHelper.AddValuesToCombobox(cmbLeverageType); 42 | } 43 | 44 | private async void btnCreate_Click(object sender, EventArgs e) 45 | { 46 | await _presenter.OnCreate(); 47 | } 48 | 49 | private void btnClear_Click(object sender, EventArgs e) 50 | { 51 | _presenter.OnClearClick(); 52 | } 53 | 54 | private void txtQuoteCurrency_TextChanged(object sender, EventArgs e) 55 | { 56 | _presenter.OnQuoteCurrencyChanged(); 57 | } 58 | 59 | private void cmbStrategy_SelectedIndexChanged(object sender, EventArgs e) 60 | { 61 | if (cmbStrategy.DataBindings.Count > 0) cmbStrategy.DataBindings[0].WriteValue(); 62 | _presenter.OnStrategyChanged(); 63 | } 64 | 65 | private void chkTrailing_CheckedChanged(object sender, EventArgs e) 66 | { 67 | lblTrailing.Enabled = chkTrailing.Checked; 68 | numTrailingDeviation.Enabled = chkTrailing.Checked; 69 | lblTrailingUnit.Enabled = chkTrailing.Checked; 70 | } 71 | 72 | private void btnAbout_Click(object sender, EventArgs e) 73 | { 74 | _presenter.OnAboutClicked(); 75 | } 76 | 77 | private void btnAddStartCondition_Click(object sender, EventArgs e) 78 | { 79 | _presenter.OnAddStartConditionClicked(); 80 | } 81 | 82 | private void btnRemove_Click(object sender, EventArgs e) 83 | { 84 | _presenter.OnRemoveStartCondition(); 85 | } 86 | 87 | private void txtBotname_TextChanged(object sender, EventArgs e) 88 | { 89 | if (txtBotname.DataBindings.Count > 0) txtBotname.DataBindings[0].WriteValue(); 90 | _presenter.OnBotNameChanged(); 91 | } 92 | 93 | private async void linkLabel3Commas_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 94 | { 95 | await _presenter.On3CommasLinkClicked(); 96 | } 97 | 98 | private void linkLabelBinance_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 99 | { 100 | _presenter.OnBinanceLinkClicked(); 101 | } 102 | 103 | private void linkLabelHuobi_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 104 | { 105 | _presenter.OnHuobiLinkClicked(); 106 | } 107 | 108 | private void chkBuyBase_CheckedChanged(object sender, EventArgs e) 109 | { 110 | _presenter.OnBuyBaseChanged(chkBuyBase.Checked); 111 | } 112 | 113 | public List SelectedStartConditions => listViewStartConditions.SelectedItems.Cast().Select(selectedItem => (BotStrategy)selectedItem.Tag).ToList(); 114 | public BotSettings SelectedTemplate => (BotSettings)cmbTemplates.SelectedItem; 115 | public bool UsePaperTrading => chkUsePaperTrading.Checked; 116 | public bool IsBinanceSelected => rbBinance.Checked; 117 | public bool IsHuobiSelected => rbHuobi.Checked; 118 | public int StartConditionsCount => listViewStartConditions.Items.Count; 119 | public int NumberOfBotsToCreate => (int)numAmount.Value; 120 | public bool Enable => chkEnable.Checked; 121 | 122 | public void SetNamePreview(string name) 123 | { 124 | lblBotNamePreview.Text = name; 125 | } 126 | 127 | public void BindAccountsAndSetSelection(List accounts, int? selectedAccount) 128 | { 129 | cmbExchange.DataSource = accounts; 130 | cmbExchange.ValueMember = nameof(Account.Id); 131 | cmbExchange.DisplayMember = nameof(Account.Name); 132 | cmbExchange.SelectedItem = selectedAccount; 133 | } 134 | 135 | public void ClearLog() 136 | { 137 | txtOutput.Clear(); 138 | } 139 | 140 | public void RefreshQuoteCurrencyLabel(string text) 141 | { 142 | lblQuoteCurrency.Text = text; 143 | lblQuoteCurrency.Visible = true; 144 | } 145 | 146 | public void SetControlStateToBuyBaseCurrency(bool isEnabled) 147 | { 148 | lblBuyTitle.Enabled = isEnabled; 149 | lblQuoteCurrency.Enabled = isEnabled; 150 | numAmountToBuy.Enabled = isEnabled; 151 | } 152 | 153 | public void SetCreateInProgress(bool inProgress) 154 | { 155 | panelMain.Enabled = !inProgress; 156 | groupBoxSettings.Enabled = !inProgress; 157 | groupBoxCredentials.Enabled = !inProgress; 158 | progressBar.Visible = inProgress; 159 | btnCancel.Text = "Cancel"; 160 | btnCancel.Enabled = inProgress; 161 | btnCancel.Visible = inProgress; 162 | } 163 | 164 | public void RefreshStartConditions(List startConditions) 165 | { 166 | listViewStartConditions.Clear(); 167 | foreach (var startCondition in startConditions) 168 | { 169 | listViewStartConditions.Items.Add(new ListViewItem { Tag = startCondition, Text = startCondition.Name, Name = startCondition.GetHashCode().ToString() }); 170 | } 171 | } 172 | 173 | public void RefreshTemplateCombo(List templates, bool selectFirst) 174 | { 175 | cmbTemplates.SelectedIndexChanged -= cmbTemplates_SelectedIndexChanged; 176 | cmbTemplates.DataSource = templates; 177 | cmbTemplates.ValueMember = nameof(BotSettings.Id); 178 | cmbTemplates.DisplayMember = nameof(BotSettings.Name); 179 | cmbTemplates.SelectedIndex = selectFirst && templates.Any() ? 0 : -1; 180 | btnDeleteTemplate.Enabled = selectFirst; 181 | cmbTemplates.SelectedIndexChanged += cmbTemplates_SelectedIndexChanged; 182 | } 183 | 184 | public void BindSetting(BotSettingViewModel botSetting) 185 | { 186 | // Accounts will not be bound due to lack of nullable support 187 | if (botSetting.ExchangeAccountId.HasValue && cmbExchange.Items.Count > 0) 188 | { 189 | cmbExchange.SelectedItem = cmbExchange.Items.Cast().Single(x => x.Id == botSetting.ExchangeAccountId); 190 | } 191 | else 192 | { 193 | cmbExchange.SelectedItem = null; 194 | } 195 | 196 | // Deal Start Conditions will not be bound 197 | RefreshStartConditions(botSetting.DealStartConditions); 198 | 199 | #region Regular DataBinding by code 200 | 201 | chkCheckForExistingBots.DataBindings.Clear(); 202 | chkCheckForExistingBots.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.SkipExistingPairs)); 203 | 204 | chkSkipBlacklistedPairs.DataBindings.Clear(); 205 | chkSkipBlacklistedPairs.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.SkipBlacklistedPairs)); 206 | 207 | chkCheckForBaseStablecoin.DataBindings.Clear(); 208 | chkCheckForBaseStablecoin.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.SkipBaseStablecoin)); 209 | 210 | txtQuoteCurrency.DataBindings.Clear(); 211 | txtQuoteCurrency.DataBindings.Add(nameof(TextBox.Text), botSetting, nameof(BotSettingViewModel.QuoteCurrency)); 212 | 213 | cmbStrategy.SelectedIndexChanged -= cmbStrategy_SelectedIndexChanged; 214 | cmbStrategy.DataBindings.Clear(); 215 | cmbStrategy.DataBindings.Add(nameof(ComboBox.SelectedItem), botSetting, nameof(BotSettingViewModel.Strategy), false, DataSourceUpdateMode.OnPropertyChanged); 216 | cmbStrategy.SelectedIndexChanged += cmbStrategy_SelectedIndexChanged; 217 | 218 | txtBotname.DataBindings.Clear(); 219 | txtBotname.DataBindings.Add(nameof(TextBox.Text), botSetting, nameof(BotSettingViewModel.Botname), true, DataSourceUpdateMode.OnPropertyChanged); 220 | 221 | cmbStartOrderType.DataBindings.Clear(); 222 | cmbStartOrderType.DataBindings.Add(nameof(ComboBox.SelectedItem), botSetting, nameof(BotSettingViewModel.StartOrderType)); 223 | 224 | numBaseOrderVolume.DataBindings.Clear(); 225 | numBaseOrderVolume.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.BaseOrderSize), false, DataSourceUpdateMode.OnPropertyChanged); 226 | 227 | numSafetyOrderVolume.DataBindings.Clear(); 228 | numSafetyOrderVolume.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.SafetyOrderSize), false, DataSourceUpdateMode.OnPropertyChanged); 229 | 230 | numTargetProfit.DataBindings.Clear(); 231 | numTargetProfit.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.TargetProfit), false, DataSourceUpdateMode.OnPropertyChanged); 232 | 233 | chkTrailing.DataBindings.Clear(); 234 | chkTrailing.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.TrailingEnabled)); 235 | 236 | numTrailingDeviation.DataBindings.Clear(); 237 | numTrailingDeviation.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.TrailingDeviation), false, DataSourceUpdateMode.OnPropertyChanged); 238 | 239 | numMaxSafetyTradesCount.DataBindings.Clear(); 240 | numMaxSafetyTradesCount.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.MaxSafetyTradesCount), false, DataSourceUpdateMode.OnPropertyChanged); 241 | 242 | numMaxActiveSafetyTradesCount.DataBindings.Clear(); 243 | numMaxActiveSafetyTradesCount.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.MaxActiveSafetyTradesCount), false, DataSourceUpdateMode.OnPropertyChanged); 244 | 245 | numPriceDeviationToOpenSafetyOrders.DataBindings.Clear(); 246 | numPriceDeviationToOpenSafetyOrders.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.PriceDeviationToOpenSafetyOrders), false, DataSourceUpdateMode.OnPropertyChanged); 247 | 248 | numSafetyOrderVolumeScale.DataBindings.Clear(); 249 | numSafetyOrderVolumeScale.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.SafetyOrderVolumeScale), false, DataSourceUpdateMode.OnPropertyChanged); 250 | 251 | numSafetyOrderStepScale.DataBindings.Clear(); 252 | numSafetyOrderStepScale.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.SafetyOrderStepScale), false, DataSourceUpdateMode.OnPropertyChanged); 253 | 254 | numCooldownBetweenDeals.DataBindings.Clear(); 255 | numCooldownBetweenDeals.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.CooldownBetweenDeals), false, DataSourceUpdateMode.OnPropertyChanged); 256 | 257 | chkBuyBase.DataBindings.Clear(); 258 | chkBuyBase.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.BuyBaseCurrency)); 259 | 260 | numAmountToBuy.DataBindings.Clear(); 261 | numAmountToBuy.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.BaseCurrencyToBuy), false, DataSourceUpdateMode.OnPropertyChanged); 262 | 263 | chkStopLossEnabled.DataBindings.Clear(); 264 | chkStopLossEnabled.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.StopLossEnabled)); 265 | 266 | numStopLossPercentage.DataBindings.Clear(); 267 | numStopLossPercentage.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.StopLossPercentage), false, DataSourceUpdateMode.OnPropertyChanged); 268 | 269 | cmbStopLossType.DataBindings.Clear(); 270 | cmbStopLossType.DataBindings.Add(nameof(ComboBox.SelectedItem), botSetting, nameof(BotSettingViewModel.StopLossAction)); 271 | 272 | chkStopLossTimeoutEnabled.DataBindings.Clear(); 273 | chkStopLossTimeoutEnabled.DataBindings.Add(nameof(CheckBox.Checked), botSetting, nameof(BotSettingViewModel.StopLossTimeoutEnabled)); 274 | 275 | numStopLossTimeout.DataBindings.Clear(); 276 | numStopLossTimeout.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.StopLossTimeout), false, DataSourceUpdateMode.OnPropertyChanged); 277 | 278 | cmbLeverageType.DataBindings.Clear(); 279 | cmbLeverageType.DataBindings.Add(nameof(ComboBox.SelectedItem), botSetting, nameof(BotSettingViewModel.LeverageType)); 280 | 281 | numCustomLeverageValue.DataBindings.Clear(); 282 | numCustomLeverageValue.DataBindings.Add(nameof(NumericUpDown.Value), botSetting, nameof(BotSettingViewModel.LeverageCustomValue), false, DataSourceUpdateMode.OnPropertyChanged); 283 | 284 | #endregion 285 | } 286 | 287 | private void cmbTemplates_SelectedIndexChanged(object sender, EventArgs e) 288 | { 289 | var selectedTemplate = cmbTemplates.SelectedItem as BotSettings; 290 | btnDeleteTemplate.Enabled = selectedTemplate != null; 291 | _presenter.OnTemplateChange(selectedTemplate); 292 | } 293 | 294 | private void cmbExchange_SelectedIndexChanged(object sender, EventArgs e) 295 | { 296 | _presenter.OnAccountChanged(cmbExchange.SelectedItem as Account); 297 | } 298 | 299 | private void btnSave_Click(object sender, EventArgs e) 300 | { 301 | _presenter.OnSave(); 302 | } 303 | 304 | private void btnDeleteTemplate_Click(object sender, EventArgs e) 305 | { 306 | _presenter.OnDeleteTemplate(); 307 | } 308 | 309 | private void chkStopLossTimeoutEnabled_CheckedChanged(object sender, EventArgs e) 310 | { 311 | numStopLossTimeout.Enabled = chkStopLossTimeoutEnabled.Checked; 312 | } 313 | 314 | private void cmbLeverageType_SelectedIndexChanged(object sender, EventArgs e) 315 | { 316 | var isLeverageNotSpecified = (LeverageType)cmbLeverageType.SelectedItem == LeverageType.NotSpecified; 317 | lblLeverageCustomValue.Enabled = !isLeverageNotSpecified; 318 | numCustomLeverageValue.Enabled = !isLeverageNotSpecified; 319 | } 320 | 321 | private async void btnPreview_Click(object sender, EventArgs e) 322 | { 323 | await _presenter.OnPreview(); 324 | } 325 | 326 | private void btnCancel_Click(object sender, EventArgs e) 327 | { 328 | btnCancel.Enabled = false; 329 | btnCancel.Text = "Cancelled!"; 330 | _presenter.OnCancelOperation(); 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/MainForm/MainFormPresenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using _3Commas.BotCreator._3CommasLayer.Implementations; 8 | using _3Commas.BotCreator.ExchangeLayer; 9 | using _3Commas.BotCreator.Infrastructure; 10 | using _3Commas.BotCreator.Misc; 11 | using _3Commas.BotCreator.Services.BotSettingService; 12 | using _3Commas.BotCreator.Services.MessageBoxService; 13 | using AutoMapper; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.Logging; 16 | using XCommas.Net.Objects; 17 | 18 | namespace _3Commas.BotCreator.Views.MainForm 19 | { 20 | public class MainFormPresenter : PresenterBase 21 | { 22 | private readonly Keys _keys = new Keys(); 23 | private readonly ILogger _logger; 24 | private readonly IMessageBoxService _mbs; 25 | private readonly IBotSettingService _bss; 26 | private readonly IMapper _mapper; 27 | private List _accounts = new List(); 28 | private BotSettingViewModel _settings = new BotSettingViewModel(); 29 | private CancellationToken _cancellationToken; 30 | private CancellationTokenSource _cancellationTokenSource; 31 | 32 | public MainFormPresenter(IMainForm view, ILogger logger, IMessageBoxService mbs, IBotSettingService bss, IMapper mapper) : base(view) 33 | { 34 | _logger = logger; 35 | _mbs = mbs; 36 | _bss = bss; 37 | _mapper = mapper; 38 | _keys.ApiKey3Commas = Properties.Settings.Default.ApiKey3Commas; 39 | _keys.Secret3Commas = Properties.Settings.Default.Secret3Commas; 40 | _keys.ApiKeyBinance = Properties.Settings.Default.ApiKeyBinance; 41 | _keys.SecretBinance = Properties.Settings.Default.SecretBinance; 42 | _keys.ApiKeyHuobi = Properties.Settings.Default.ApiKeyHuobi; 43 | _keys.SecretHuobi = Properties.Settings.Default.SecretHuobi; 44 | } 45 | 46 | internal async Task OnViewReady() 47 | { 48 | View.BindCombos(); 49 | await RefreshExchangesAndBlacklist(); 50 | RefreshTemplates(false); 51 | View.BindSetting(_settings); 52 | RefreshNamePreview(); 53 | } 54 | 55 | private void RefreshTemplates(bool selectFirst) 56 | { 57 | var templates = _bss.GetAll(); 58 | View.RefreshTemplateCombo(templates, selectFirst); 59 | } 60 | 61 | public void OnBotNameChanged() 62 | { 63 | RefreshNamePreview(); 64 | } 65 | 66 | private void RefreshNamePreview() 67 | { 68 | var quoteCurrency = (!String.IsNullOrWhiteSpace(_settings.QuoteCurrency)) ? _settings.QuoteCurrency + "_XXX" : ""; 69 | var strategy = _settings.Strategy; 70 | 71 | var namePreview = NameHelper.GenerateBotName(_settings.Botname, quoteCurrency, strategy); 72 | View.SetNamePreview(namePreview); 73 | } 74 | 75 | public void OnHuobiLinkClicked() 76 | { 77 | var settingsPersisted = !string.IsNullOrWhiteSpace(Properties.Settings.Default.ApiKeyHuobi); 78 | var settings = new SetApiKeyView.SetApiKeyView(settingsPersisted, "Huobi API Credentials", "Permissions Needed: Read-Only, Trade", _keys.ApiKeyHuobi, _keys.SecretHuobi); 79 | var dr = settings.ShowDialog(); 80 | if (dr == DialogResult.OK) 81 | { 82 | _keys.ApiKeyHuobi = settings.ApiKey; 83 | _keys.SecretHuobi = settings.Secret; 84 | 85 | Properties.Settings.Default.ApiKeyHuobi = settings.PersistKeys ? settings.ApiKey : ""; 86 | Properties.Settings.Default.SecretHuobi = settings.PersistKeys ? settings.Secret : ""; 87 | Properties.Settings.Default.Save(); 88 | } 89 | } 90 | 91 | public void OnBinanceLinkClicked() 92 | { 93 | var settingsPersisted = !string.IsNullOrWhiteSpace(Properties.Settings.Default.ApiKeyBinance); 94 | var settings = new SetApiKeyView.SetApiKeyView(settingsPersisted, "Binance API Credentials", "Permissions Needed: Can Read, Enable Spot Trading", _keys.ApiKeyBinance, _keys.SecretBinance); 95 | var dr = settings.ShowDialog(); 96 | if (dr == DialogResult.OK) 97 | { 98 | _keys.ApiKeyBinance = settings.ApiKey; 99 | _keys.SecretBinance = settings.Secret; 100 | 101 | Properties.Settings.Default.ApiKeyBinance = settings.PersistKeys ? settings.ApiKey : ""; 102 | Properties.Settings.Default.SecretBinance = settings.PersistKeys ? settings.Secret : ""; 103 | Properties.Settings.Default.Save(); 104 | } 105 | } 106 | 107 | public async Task On3CommasLinkClicked() 108 | { 109 | var settingsPersisted = !string.IsNullOrWhiteSpace(Properties.Settings.Default.ApiKey3Commas); 110 | var settings = new SetApiKeyView.SetApiKeyView(settingsPersisted, "3Commas API Credentials", "Permissions Needed: BotsRead, BotsWrite, AccountsRead", _keys.ApiKey3Commas, _keys.Secret3Commas); 111 | var dr = settings.ShowDialog(); 112 | if (dr == DialogResult.OK) 113 | { 114 | _keys.ApiKey3Commas = settings.ApiKey; 115 | _keys.Secret3Commas = settings.Secret; 116 | 117 | Properties.Settings.Default.ApiKey3Commas = settings.PersistKeys ? settings.ApiKey : ""; 118 | Properties.Settings.Default.Secret3Commas = settings.PersistKeys ? settings.Secret : ""; 119 | Properties.Settings.Default.Save(); 120 | 121 | await RefreshExchangesAndBlacklist(); 122 | } 123 | } 124 | 125 | private async Task RefreshExchangesAndBlacklist() 126 | { 127 | if (!String.IsNullOrWhiteSpace(_keys.Secret3Commas) && !String.IsNullOrWhiteSpace(_keys.ApiKey3Commas)) 128 | { 129 | var selection = _settings.ExchangeAccountId; 130 | var botMgr = new BotManager(_logger, new XCommasClient(_keys, View.UsePaperTrading), null); 131 | _accounts = await botMgr.RetrieveAccounts(); 132 | View.BindAccountsAndSetSelection(_accounts, selection); 133 | } 134 | else 135 | { 136 | View.BindAccountsAndSetSelection(null, null); 137 | } 138 | } 139 | 140 | private bool IsValid() 141 | { 142 | var errors = new List(); 143 | if (_accounts.Count == 0) 144 | { 145 | errors.Add("Please specify 3Commas API Credentials and select your 3Commas Exchange Account."); 146 | } 147 | else if (_settings.ExchangeAccountId == null) 148 | { 149 | errors.Add("3Commas Exchange Account not selected."); 150 | } 151 | 152 | if (_settings.BuyBaseCurrency && View.UsePaperTrading) 153 | { 154 | errors.Add("The option to buy base coins is not allowed in combination with paper trading."); 155 | } 156 | 157 | if (!View.IsBinanceSelected && !View.IsHuobiSelected) 158 | { 159 | errors.Add("Choose your preferred Exchange."); 160 | } 161 | 162 | if (View.IsBinanceSelected && (String.IsNullOrWhiteSpace(_keys.ApiKeyBinance) || String.IsNullOrWhiteSpace(_keys.SecretBinance))) 163 | { 164 | errors.Add("API Credentials for Binance missing"); 165 | } 166 | 167 | if (View.IsHuobiSelected && (String.IsNullOrWhiteSpace(_keys.ApiKeyHuobi) || String.IsNullOrWhiteSpace(_keys.SecretHuobi))) 168 | { 169 | errors.Add("API Credentials for Huobi missing"); 170 | } 171 | 172 | if (string.IsNullOrWhiteSpace(_settings.Botname)) errors.Add("Bot name missing"); 173 | if (string.IsNullOrWhiteSpace(_settings.QuoteCurrency)) errors.Add("Quote Currency missing"); 174 | if (View.StartConditionsCount == 0) errors.Add("Deal Start Condition missing"); 175 | if (_settings.StopLossEnabled) 176 | { 177 | var maxDeviation = CalculateMaxSoDeviation(); 178 | if (_settings.StopLossPercentage <= maxDeviation) 179 | { 180 | errors.Add($"Stop Loss should be below the last safety order ({maxDeviation})"); 181 | } 182 | } 183 | 184 | if (errors.Any()) 185 | { 186 | _mbs.ShowError(String.Join(Environment.NewLine, errors), "Validation Error"); 187 | } 188 | 189 | return !errors.Any(); 190 | } 191 | 192 | private decimal CalculateMaxSoDeviation() 193 | { 194 | // todo 195 | return 0; 196 | } 197 | 198 | public void OnClearClick() 199 | { 200 | View.ClearLog(); 201 | } 202 | 203 | public void OnQuoteCurrencyChanged() 204 | { 205 | View.RefreshQuoteCurrencyLabel(_settings.QuoteCurrency); 206 | RefreshNamePreview(); 207 | } 208 | 209 | public void OnBuyBaseChanged(bool isBuyEnabled) 210 | { 211 | View.SetControlStateToBuyBaseCurrency(isBuyEnabled); 212 | } 213 | 214 | public void OnAddStartConditionClicked() 215 | { 216 | var frm = Program.ServiceProvider.GetRequiredService(); 217 | if (frm.ShowDialog(View) == DialogResult.OK) 218 | { 219 | _settings.DealStartConditions.Add(frm.Strategy); 220 | View.RefreshStartConditions(_settings.DealStartConditions); 221 | } 222 | } 223 | 224 | public void OnRemoveStartCondition() 225 | { 226 | foreach (var item in View.SelectedStartConditions) 227 | { 228 | _settings.DealStartConditions.Remove(item); 229 | } 230 | View.RefreshStartConditions(_settings.DealStartConditions); 231 | } 232 | 233 | public async Task OnCreate() 234 | { 235 | if (IsValid()) 236 | { 237 | var dr = _mbs.ShowQuestion($"Do you really want to create {View.NumberOfBotsToCreate} Bots now?"); 238 | if (dr == DialogResult.Yes) 239 | { 240 | View.SetCreateInProgress(true); 241 | 242 | IExchange exchange = null; 243 | if (View.IsBinanceSelected) 244 | { 245 | exchange = new ExchangeLayer.Implementations.Binance(_keys); 246 | } 247 | else if (View.IsHuobiSelected) 248 | { 249 | exchange = new ExchangeLayer.Implementations.Huobi(_keys); 250 | } 251 | 252 | var botMgr = new BotManager(_logger, new XCommasClient(_keys, View.UsePaperTrading), exchange); 253 | 254 | try 255 | { 256 | _cancellationTokenSource = new CancellationTokenSource(); 257 | var cancellationToken = _cancellationTokenSource.Token; 258 | await botMgr.CreateBots(View.NumberOfBotsToCreate, View.Enable, _settings, cancellationToken); 259 | View.SetCreateInProgress(false); 260 | _mbs.ShowInformation("Operation finished! See output section for details."); 261 | } 262 | catch (Exception exception) 263 | { 264 | _logger.LogError(exception.Message); 265 | _mbs.ShowError("Error: " + exception); 266 | } 267 | finally 268 | { 269 | View.SetCreateInProgress(false); 270 | } 271 | } 272 | } 273 | } 274 | 275 | public async Task OnPreview() 276 | { 277 | if (IsValid()) 278 | { 279 | View.SetCreateInProgress(true); 280 | _logger.LogInformation("PREVIEW BEGIN"); 281 | 282 | IExchange exchange = null; 283 | if (View.IsBinanceSelected) 284 | { 285 | exchange = new ExchangeLayer.Implementations.Binance(_keys); 286 | } 287 | else if (View.IsHuobiSelected) 288 | { 289 | exchange = new ExchangeLayer.Implementations.Huobi(_keys); 290 | } 291 | 292 | var botMgr = new BotManager(_logger, new XCommasClient(_keys, View.UsePaperTrading), exchange); 293 | 294 | try 295 | { 296 | _cancellationTokenSource = new CancellationTokenSource(); 297 | var cancellationToken = _cancellationTokenSource.Token; 298 | await botMgr.PreviewBotsCreation(View.NumberOfBotsToCreate, _settings, cancellationToken); 299 | View.SetCreateInProgress(false); 300 | _mbs.ShowInformation("Operation finished! See output section for details."); 301 | } 302 | catch (Exception exception) 303 | { 304 | _logger.LogError(exception.Message); 305 | _mbs.ShowError("Error: " + exception); 306 | } 307 | finally 308 | { 309 | View.SetCreateInProgress(false); 310 | } 311 | 312 | _logger.LogInformation("PREVIEW END"); 313 | } 314 | } 315 | 316 | public void OnAccountChanged(Account cmbExchangeSelectedItem) 317 | { 318 | _settings.ExchangeAccountId = cmbExchangeSelectedItem?.Id; 319 | } 320 | 321 | public void OnSave() 322 | { 323 | var frm = Program.ServiceProvider.GetRequiredService(); 324 | frm.Init(_settings); 325 | var dr = frm.ShowDialog(View); 326 | if (dr == DialogResult.OK) 327 | { 328 | RefreshTemplates(true); 329 | } 330 | } 331 | 332 | public void OnTemplateChange(BotSettings botSettings) 333 | { 334 | _settings = botSettings == null ? new BotSettingViewModel() : _mapper.Map(botSettings); 335 | 336 | View.BindSetting(_settings); 337 | } 338 | 339 | public void OnDeleteTemplate() 340 | { 341 | var dr = _mbs.ShowQuestion($"Do you really want to delete template '{View.SelectedTemplate.Name}' ?"); 342 | if (dr == DialogResult.Yes) 343 | { 344 | _bss.Delete(View.SelectedTemplate.Id); 345 | RefreshTemplates(false); 346 | } 347 | } 348 | 349 | public void OnStrategyChanged() 350 | { 351 | RefreshNamePreview(); 352 | 353 | _settings.DealStartConditions.Clear(); 354 | View.RefreshStartConditions(_settings.DealStartConditions); 355 | } 356 | 357 | public void OnAboutClicked() 358 | { 359 | var frm = Program.ServiceProvider.GetRequiredService(); 360 | frm.ShowDialog(View); 361 | } 362 | 363 | public void OnCancelOperation() 364 | { 365 | _cancellationTokenSource.Cancel(); 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SaveTemplateView/SaveTemplateView.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator.Views.SaveTemplateView 2 | { 3 | partial class SaveTemplateView 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.button1 = new System.Windows.Forms.Button(); 32 | this.okButton = new System.Windows.Forms.Button(); 33 | this.txtNewTemplate = new System.Windows.Forms.TextBox(); 34 | this.cmbExistingTemplate = new System.Windows.Forms.ComboBox(); 35 | this.radioButtonCreateNew = new System.Windows.Forms.RadioButton(); 36 | this.radioButtonOverwriteExisting = new System.Windows.Forms.RadioButton(); 37 | this.label1 = new System.Windows.Forms.Label(); 38 | this.SuspendLayout(); 39 | // 40 | // button1 41 | // 42 | this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 43 | this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel; 44 | this.button1.Location = new System.Drawing.Point(152, 123); 45 | this.button1.Name = "button1"; 46 | this.button1.Size = new System.Drawing.Size(75, 23); 47 | this.button1.TabIndex = 28; 48 | this.button1.Text = "&Cancel"; 49 | // 50 | // okButton 51 | // 52 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 53 | this.okButton.Location = new System.Drawing.Point(233, 123); 54 | this.okButton.Name = "okButton"; 55 | this.okButton.Size = new System.Drawing.Size(75, 23); 56 | this.okButton.TabIndex = 27; 57 | this.okButton.Text = "&OK"; 58 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 59 | // 60 | // txtNewTemplate 61 | // 62 | this.txtNewTemplate.Location = new System.Drawing.Point(167, 31); 63 | this.txtNewTemplate.Name = "txtNewTemplate"; 64 | this.txtNewTemplate.Size = new System.Drawing.Size(120, 20); 65 | this.txtNewTemplate.TabIndex = 30; 66 | this.txtNewTemplate.Visible = false; 67 | // 68 | // cmbExistingTemplate 69 | // 70 | this.cmbExistingTemplate.FormattingEnabled = true; 71 | this.cmbExistingTemplate.Location = new System.Drawing.Point(167, 69); 72 | this.cmbExistingTemplate.Name = "cmbExistingTemplate"; 73 | this.cmbExistingTemplate.Size = new System.Drawing.Size(121, 21); 74 | this.cmbExistingTemplate.TabIndex = 31; 75 | this.cmbExistingTemplate.Visible = false; 76 | // 77 | // radioButtonCreateNew 78 | // 79 | this.radioButtonCreateNew.AutoSize = true; 80 | this.radioButtonCreateNew.Location = new System.Drawing.Point(28, 32); 81 | this.radioButtonCreateNew.Name = "radioButtonCreateNew"; 82 | this.radioButtonCreateNew.Size = new System.Drawing.Size(122, 17); 83 | this.radioButtonCreateNew.TabIndex = 33; 84 | this.radioButtonCreateNew.TabStop = true; 85 | this.radioButtonCreateNew.Text = "Create new template"; 86 | this.radioButtonCreateNew.UseVisualStyleBackColor = true; 87 | this.radioButtonCreateNew.CheckedChanged += new System.EventHandler(this.radioButtonCreateNew_CheckedChanged); 88 | // 89 | // radioButtonOverwriteExisting 90 | // 91 | this.radioButtonOverwriteExisting.AutoSize = true; 92 | this.radioButtonOverwriteExisting.Location = new System.Drawing.Point(28, 70); 93 | this.radioButtonOverwriteExisting.Name = "radioButtonOverwriteExisting"; 94 | this.radioButtonOverwriteExisting.Size = new System.Drawing.Size(108, 17); 95 | this.radioButtonOverwriteExisting.TabIndex = 34; 96 | this.radioButtonOverwriteExisting.TabStop = true; 97 | this.radioButtonOverwriteExisting.Text = "Overwrite existing"; 98 | this.radioButtonOverwriteExisting.UseVisualStyleBackColor = true; 99 | this.radioButtonOverwriteExisting.CheckedChanged += new System.EventHandler(this.radioButtonCreateNew_CheckedChanged); 100 | // 101 | // label1 102 | // 103 | this.label1.AutoSize = true; 104 | this.label1.Location = new System.Drawing.Point(45, 52); 105 | this.label1.Name = "label1"; 106 | this.label1.Size = new System.Drawing.Size(16, 13); 107 | this.label1.TabIndex = 35; 108 | this.label1.Text = "or"; 109 | // 110 | // SaveTemplateView 111 | // 112 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 113 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 114 | this.ClientSize = new System.Drawing.Size(320, 158); 115 | this.Controls.Add(this.label1); 116 | this.Controls.Add(this.radioButtonOverwriteExisting); 117 | this.Controls.Add(this.radioButtonCreateNew); 118 | this.Controls.Add(this.txtNewTemplate); 119 | this.Controls.Add(this.cmbExistingTemplate); 120 | this.Controls.Add(this.button1); 121 | this.Controls.Add(this.okButton); 122 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 123 | this.MaximizeBox = false; 124 | this.MinimizeBox = false; 125 | this.Name = "SaveTemplateView"; 126 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 127 | this.Text = "Save Template"; 128 | this.ResumeLayout(false); 129 | this.PerformLayout(); 130 | 131 | } 132 | 133 | #endregion 134 | 135 | private System.Windows.Forms.Button button1; 136 | private System.Windows.Forms.Button okButton; 137 | private System.Windows.Forms.TextBox txtNewTemplate; 138 | private System.Windows.Forms.ComboBox cmbExistingTemplate; 139 | private System.Windows.Forms.RadioButton radioButtonCreateNew; 140 | private System.Windows.Forms.RadioButton radioButtonOverwriteExisting; 141 | private System.Windows.Forms.Label label1; 142 | } 143 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SaveTemplateView/SaveTemplateView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | using _3Commas.BotCreator.Services.BotSettingService; 6 | using _3Commas.BotCreator.Services.MessageBoxService; 7 | using _3Commas.BotCreator.Views.MainForm; 8 | using AutoMapper; 9 | 10 | namespace _3Commas.BotCreator.Views.SaveTemplateView 11 | { 12 | public partial class SaveTemplateView : Form 13 | { 14 | private readonly IBotSettingService _templateService; 15 | private BotSettingViewModel _botSettings; 16 | private readonly IMessageBoxService _mbs = new MessageBoxService(); 17 | private readonly IMapper _mapper; 18 | 19 | public SaveTemplateView(IBotSettingService templateService, IMapper mapper) 20 | { 21 | _mapper = mapper; 22 | _templateService = templateService; 23 | InitializeComponent(); 24 | 25 | cmbExistingTemplate.DisplayMember = nameof(BotSettings.Name); 26 | cmbExistingTemplate.ValueMember = nameof(BotSettings.Id); 27 | cmbExistingTemplate.DataSource = _templateService.GetAll(); 28 | } 29 | 30 | public void Init(BotSettingViewModel settings) 31 | { 32 | _botSettings = settings; 33 | } 34 | 35 | private void radioButtonCreateNew_CheckedChanged(object sender, EventArgs e) 36 | { 37 | txtNewTemplate.Visible = sender == radioButtonCreateNew; 38 | cmbExistingTemplate.Visible = sender == radioButtonOverwriteExisting; 39 | } 40 | 41 | private void okButton_Click(object sender, EventArgs e) 42 | { 43 | if (IsValid()) 44 | { 45 | bool result = false; 46 | 47 | var settings = _mapper.Map(_botSettings); 48 | 49 | if (radioButtonCreateNew.Checked) 50 | { 51 | result = _templateService.Insert(settings, txtNewTemplate.Text); 52 | } 53 | if (radioButtonOverwriteExisting.Checked) 54 | { 55 | result = _templateService.Update(settings, (Guid)cmbExistingTemplate.SelectedValue); 56 | } 57 | 58 | if (!result) 59 | { 60 | _mbs.ShowError("An error occured while saving the bot settings"); 61 | } 62 | else 63 | { 64 | this.DialogResult = DialogResult.OK; 65 | } 66 | } 67 | } 68 | 69 | private bool IsValid() 70 | { 71 | var errors = new List(); 72 | 73 | if (!radioButtonCreateNew.Checked && !radioButtonOverwriteExisting.Checked) errors.Add("Please select an option"); 74 | if (radioButtonCreateNew.Checked && string.IsNullOrWhiteSpace(txtNewTemplate.Text)) errors.Add("Please specify a name for the new Template"); 75 | if (radioButtonCreateNew.Checked && cmbExistingTemplate.Items.Cast().Any(x => x.Name == txtNewTemplate.Text)) errors.Add("This name is already in use"); 76 | if (radioButtonOverwriteExisting.Checked && cmbExistingTemplate.SelectedItem == null) errors.Add("Existing Template not selected"); 77 | 78 | if (errors.Any()) 79 | { 80 | _mbs.ShowError(String.Join(Environment.NewLine, errors), "Validation Error"); 81 | } 82 | 83 | return !errors.Any(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SaveTemplateView/SaveTemplateView.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SetApiKeyView/SetApiKeyView.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace _3Commas.BotCreator.Views.SetApiKeyView 2 | { 3 | partial class SetApiKeyView 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.txtBinanceApiKey = new System.Windows.Forms.TextBox(); 32 | this.txtBinanceSecret = new System.Windows.Forms.TextBox(); 33 | this.btnApply = new System.Windows.Forms.Button(); 34 | this.pictureBox2 = new System.Windows.Forms.PictureBox(); 35 | this.label1 = new System.Windows.Forms.Label(); 36 | this.lblPermissionsNeeded = new System.Windows.Forms.Label(); 37 | this.label2 = new System.Windows.Forms.Label(); 38 | this.btnCancel = new System.Windows.Forms.Button(); 39 | this.chkPersist = new System.Windows.Forms.CheckBox(); 40 | ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); 41 | this.SuspendLayout(); 42 | // 43 | // txtBinanceApiKey 44 | // 45 | this.txtBinanceApiKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 46 | | System.Windows.Forms.AnchorStyles.Right))); 47 | this.txtBinanceApiKey.Location = new System.Drawing.Point(79, 12); 48 | this.txtBinanceApiKey.Name = "txtBinanceApiKey"; 49 | this.txtBinanceApiKey.Size = new System.Drawing.Size(483, 20); 50 | this.txtBinanceApiKey.TabIndex = 21; 51 | // 52 | // txtBinanceSecret 53 | // 54 | this.txtBinanceSecret.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 55 | | System.Windows.Forms.AnchorStyles.Right))); 56 | this.txtBinanceSecret.Location = new System.Drawing.Point(79, 38); 57 | this.txtBinanceSecret.Name = "txtBinanceSecret"; 58 | this.txtBinanceSecret.Size = new System.Drawing.Size(483, 20); 59 | this.txtBinanceSecret.TabIndex = 22; 60 | // 61 | // btnApply 62 | // 63 | this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 64 | this.btnApply.Location = new System.Drawing.Point(487, 95); 65 | this.btnApply.Name = "btnApply"; 66 | this.btnApply.Size = new System.Drawing.Size(75, 23); 67 | this.btnApply.TabIndex = 27; 68 | this.btnApply.Text = "Apply"; 69 | this.btnApply.UseVisualStyleBackColor = true; 70 | this.btnApply.Click += new System.EventHandler(this.btnApply_Click); 71 | // 72 | // pictureBox2 73 | // 74 | this.pictureBox2.Image = global::_3Commas.BotCreator.Properties.Resources.Info_16x16; 75 | this.pictureBox2.Location = new System.Drawing.Point(79, 64); 76 | this.pictureBox2.Name = "pictureBox2"; 77 | this.pictureBox2.Size = new System.Drawing.Size(16, 16); 78 | this.pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; 79 | this.pictureBox2.TabIndex = 28; 80 | this.pictureBox2.TabStop = false; 81 | // 82 | // label1 83 | // 84 | this.label1.AutoSize = true; 85 | this.label1.Location = new System.Drawing.Point(28, 15); 86 | this.label1.Name = "label1"; 87 | this.label1.Size = new System.Drawing.Size(45, 13); 88 | this.label1.TabIndex = 23; 89 | this.label1.Text = "API Key"; 90 | this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 91 | // 92 | // lblPermissionsNeeded 93 | // 94 | this.lblPermissionsNeeded.AutoSize = true; 95 | this.lblPermissionsNeeded.Location = new System.Drawing.Point(101, 66); 96 | this.lblPermissionsNeeded.Name = "lblPermissionsNeeded"; 97 | this.lblPermissionsNeeded.Size = new System.Drawing.Size(115, 13); 98 | this.lblPermissionsNeeded.TabIndex = 27; 99 | this.lblPermissionsNeeded.Text = ""; 100 | // 101 | // label2 102 | // 103 | this.label2.AutoSize = true; 104 | this.label2.Location = new System.Drawing.Point(35, 42); 105 | this.label2.Name = "label2"; 106 | this.label2.Size = new System.Drawing.Size(38, 13); 107 | this.label2.TabIndex = 24; 108 | this.label2.Text = "Secret"; 109 | this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 110 | // 111 | // btnCancel 112 | // 113 | this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 114 | this.btnCancel.Location = new System.Drawing.Point(406, 95); 115 | this.btnCancel.Name = "btnCancel"; 116 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 117 | this.btnCancel.TabIndex = 30; 118 | this.btnCancel.Text = "Cancel"; 119 | this.btnCancel.UseVisualStyleBackColor = true; 120 | this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 121 | // 122 | // chkPersist 123 | // 124 | this.chkPersist.AutoSize = true; 125 | this.chkPersist.Location = new System.Drawing.Point(79, 99); 126 | this.chkPersist.Name = "chkPersist"; 127 | this.chkPersist.Size = new System.Drawing.Size(98, 17); 128 | this.chkPersist.TabIndex = 31; 129 | this.chkPersist.Text = "Persist Settings"; 130 | this.chkPersist.UseVisualStyleBackColor = true; 131 | this.chkPersist.CheckedChanged += new System.EventHandler(this.chkPersist_CheckedChanged); 132 | // 133 | // Settings 134 | // 135 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 136 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 137 | this.ClientSize = new System.Drawing.Size(574, 130); 138 | this.Controls.Add(this.chkPersist); 139 | this.Controls.Add(this.pictureBox2); 140 | this.Controls.Add(this.btnCancel); 141 | this.Controls.Add(this.label1); 142 | this.Controls.Add(this.lblPermissionsNeeded); 143 | this.Controls.Add(this.btnApply); 144 | this.Controls.Add(this.label2); 145 | this.Controls.Add(this.txtBinanceApiKey); 146 | this.Controls.Add(this.txtBinanceSecret); 147 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 148 | this.MaximizeBox = false; 149 | this.MinimizeBox = false; 150 | this.Name = "SetApiKeyView"; 151 | this.ShowIcon = false; 152 | this.ShowInTaskbar = false; 153 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 154 | this.Text = "Settings"; 155 | ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); 156 | this.ResumeLayout(false); 157 | this.PerformLayout(); 158 | 159 | } 160 | 161 | #endregion 162 | private System.Windows.Forms.TextBox txtBinanceApiKey; 163 | private System.Windows.Forms.TextBox txtBinanceSecret; 164 | private System.Windows.Forms.Button btnApply; 165 | private System.Windows.Forms.Label label1; 166 | private System.Windows.Forms.Label label2; 167 | private System.Windows.Forms.PictureBox pictureBox2; 168 | private System.Windows.Forms.Label lblPermissionsNeeded; 169 | private System.Windows.Forms.Button btnCancel; 170 | private System.Windows.Forms.CheckBox chkPersist; 171 | } 172 | } -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SetApiKeyView/SetApiKeyView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Windows.Forms; 4 | 5 | namespace _3Commas.BotCreator.Views.SetApiKeyView 6 | { 7 | public partial class SetApiKeyView : Form 8 | { 9 | private readonly bool _viewLoaded; 10 | 11 | public SetApiKeyView(bool persistSettings, string title, string permissionsNeeded, string apiKey, string secret) 12 | { 13 | InitializeComponent(); 14 | chkPersist.Checked = persistSettings; 15 | Text = title; 16 | lblPermissionsNeeded.Text = permissionsNeeded; 17 | 18 | ApiKey = apiKey; 19 | Secret = secret; 20 | txtBinanceApiKey.Text = apiKey; 21 | txtBinanceSecret.Text = secret; 22 | 23 | _viewLoaded = true; 24 | } 25 | 26 | private void btnApply_Click(object sender, EventArgs e) 27 | { 28 | ApiKey = txtBinanceApiKey.Text; 29 | Secret = txtBinanceSecret.Text; 30 | 31 | DialogResult = DialogResult.OK; 32 | } 33 | 34 | public string ApiKey { get; set; } 35 | 36 | public string Secret { get; set; } 37 | 38 | public bool PersistKeys => chkPersist.Checked; 39 | 40 | private void btnCancel_Click(object sender, EventArgs e) 41 | { 42 | this.Close(); 43 | } 44 | 45 | private void chkPersist_CheckedChanged(object sender, EventArgs e) 46 | { 47 | if (_viewLoaded && chkPersist.Checked) 48 | { 49 | MessageBox.Show("Your keys will be persisted in your user directory. Be aware that other programs may potentially have access to it. In addition, after an application update, the configuration files of previous versions may still exist. Only an uninstallation will delete all configuration data." + Environment.NewLine + Environment.NewLine + 50 | "Settings location: " + System.IO.Path.GetDirectoryName(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath), "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/Views/SetApiKeyView/SetApiKeyView.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/3Commas.BotCreator/robot.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcDrexler/3Commas.BotCreator/9e2fda03e4d81f2e54452d899901633d087d81ed/src/3Commas.BotCreator/robot.ico --------------------------------------------------------------------------------