├── .gitignore ├── README.md ├── SimpleTrader ├── SimpleTrader.Domain.Tests │ ├── Services │ │ ├── AuthenticationServices │ │ │ └── AuthenticationServiceTests.cs │ │ └── TransactionServices │ │ │ ├── BuyStockServiceTests.cs │ │ │ └── SellStockServiceTests.cs │ └── SimpleTrader.Domain.Tests.csproj ├── SimpleTrader.Domain │ ├── Exceptions │ │ ├── InsufficientFundsException.cs │ │ ├── InsufficientSharesException.cs │ │ ├── InvalidPasswordException.cs │ │ ├── InvalidSymbolException.cs │ │ └── UserNotFoundException.cs │ ├── Models │ │ ├── Account.cs │ │ ├── Asset.cs │ │ ├── AssetTransaction.cs │ │ ├── DomainObject.cs │ │ ├── MajorIndex.cs │ │ └── User.cs │ ├── Services │ │ ├── AuthenticationServices │ │ │ ├── AuthenticationService.cs │ │ │ └── IAuthenticationService.cs │ │ ├── IAccountService.cs │ │ ├── IDataService.cs │ │ ├── IMajorIndexService.cs │ │ ├── IStockPriceService.cs │ │ └── TransactionServices │ │ │ ├── BuyStockService.cs │ │ │ ├── IBuyStockService.cs │ │ │ ├── ISellStockService.cs │ │ │ └── SellStockService.cs │ └── SimpleTrader.Domain.csproj ├── SimpleTrader.EntityFramework │ ├── Migrations │ │ ├── 20191128145049_initial.Designer.cs │ │ ├── 20191128145049_initial.cs │ │ ├── 20200125172134_stock-to-asset.Designer.cs │ │ ├── 20200125172134_stock-to-asset.cs │ │ ├── 20200404125938_password_hash.Designer.cs │ │ ├── 20200404125938_password_hash.cs │ │ └── SimpleTraderDbContextModelSnapshot.cs │ ├── Services │ │ ├── AccountDataService.cs │ │ ├── Common │ │ │ └── NonQueryDataService.cs │ │ └── GenericDataService.cs │ ├── SimpleTrader.EntityFramework.csproj │ ├── SimpleTraderDbContext.cs │ └── SimpleTraderDbContextFactory.cs ├── SimpleTrader.FinancialModelingPrepAPI │ ├── FinancialModelingPrepHttpClient.cs │ ├── Models │ │ └── FinancialModelingPrepAPIKey.cs │ ├── Results │ │ └── StockPriceResult.cs │ ├── Services │ │ ├── MajorIndexService.cs │ │ └── StockPriceService.cs │ └── SimpleTrader.FinancialModelingPrepAPI.csproj ├── SimpleTrader.WPF │ ├── App.xaml │ ├── App.xaml.cs │ ├── Commands │ │ ├── AsyncCommandBase.cs │ │ ├── BuyStockCommand.cs │ │ ├── LoadMajorIndexesCommand.cs │ │ ├── LoginCommand.cs │ │ ├── RegisterCommand.cs │ │ ├── RenavigateCommand.cs │ │ ├── SearchSymbolCommand.cs │ │ ├── SellStockCommand.cs │ │ └── UpdateCurrentViewModelCommand.cs │ ├── Controls │ │ ├── AssetListing.xaml │ │ ├── AssetListing.xaml.cs │ │ ├── AssetSummary.xaml │ │ ├── AssetSummary.xaml.cs │ │ ├── MajorIndexCard.xaml │ │ ├── MajorIndexCard.xaml.cs │ │ ├── MajorIndexListing.xaml │ │ ├── MajorIndexListing.xaml.cs │ │ ├── NavigationBar.xaml │ │ ├── NavigationBar.xaml.cs │ │ ├── SearchSymbolResultPanel.xaml │ │ └── SearchSymbolResultPanel.xaml.cs │ ├── Converters │ │ └── EqualValueToParameterConverter.cs │ ├── HostBuilders │ │ ├── AddConfigurationHostBuilderExtensions.cs │ │ ├── AddDbContextHostBuilderExtensions.cs │ │ ├── AddFinanceAPIHostBuilderExtensions.cs │ │ ├── AddServicesHostBuilderExtensions.cs │ │ ├── AddStoresHostBuilderExtensions.cs │ │ ├── AddViewModelsHostBuilderExtensions.cs │ │ └── AddViewsHostBuilderExtensions.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Resources │ │ └── login-background.jpg │ ├── SimpleTrader.WPF.csproj │ ├── SimpleTrader.WPF.csproj.user │ ├── State │ │ ├── Accounts │ │ │ ├── AccountStore.cs │ │ │ └── IAccountStore.cs │ │ ├── Assets │ │ │ └── AssetStore.cs │ │ ├── Authenticators │ │ │ ├── Authenticator.cs │ │ │ └── IAuthenticator.cs │ │ └── Navigators │ │ │ ├── INavigator.cs │ │ │ ├── IRenavigator.cs │ │ │ ├── Navigator.cs │ │ │ └── ViewModelDelegateRenavigator.cs │ ├── Styles │ │ ├── Common.xaml │ │ └── NavigationBar.xaml │ ├── ViewModels │ │ ├── AssetListingViewModel.cs │ │ ├── AssetSummaryViewModel.cs │ │ ├── AssetViewModel.cs │ │ ├── BuyViewModel.cs │ │ ├── Factories │ │ │ ├── ISimpleTraderViewModelFactory.cs │ │ │ └── SimpleTraderViewModelFactory.cs │ │ ├── HomeViewModel.cs │ │ ├── ISearchSymbolViewModel.cs │ │ ├── LoginViewModel.cs │ │ ├── MainViewModel.cs │ │ ├── MajorIndexListingViewModel.cs │ │ ├── MessageViewModel.cs │ │ ├── PortfolioViewModel.cs │ │ ├── RegisterViewModel.cs │ │ ├── SellViewModel.cs │ │ └── ViewModelBase.cs │ ├── Views │ │ ├── BuyView.xaml │ │ ├── BuyView.xaml.cs │ │ ├── HomeView.xaml │ │ ├── HomeView.xaml.cs │ │ ├── LoginView.xaml │ │ ├── LoginView.xaml.cs │ │ ├── PortfolioView.xaml │ │ ├── PortfolioView.xaml.cs │ │ ├── RegisterView.xaml │ │ ├── RegisterView.xaml.cs │ │ ├── SellView.xaml │ │ └── SellView.xaml.cs │ └── appsettings.json └── SimpleTrader.sln └── Wireframes └── SimpleTrader-v1.vsdx /.gitignore: -------------------------------------------------------------------------------- 1 | *.~vsdx 2 | .vs/ 3 | bin/ 4 | obj/ 5 | App.config 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleTrader 2 | A full stack WPF MVVM trading application. 3 | 4 | Follow along at https://www.youtube.com/playlist?list=PLA8ZIAm2I03jSfo18F7Y65XusYzDusYu5 5 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain.Tests/Services/AuthenticationServices/AuthenticationServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.Identity; 2 | using Moq; 3 | using NUnit.Framework; 4 | using SimpleTrader.Domain.Exceptions; 5 | using SimpleTrader.Domain.Models; 6 | using SimpleTrader.Domain.Services; 7 | using SimpleTrader.Domain.Services.AuthenticationServices; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace SimpleTrader.Domain.Tests.Services.AuthenticationServices 14 | { 15 | [TestFixture] 16 | public class AuthenticationServiceTests 17 | { 18 | private Mock _mockPasswordHasher; 19 | private Mock _mockAccountService; 20 | private AuthenticationService _authenticationService; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mockPasswordHasher = new Mock(); 26 | _mockAccountService = new Mock(); 27 | _authenticationService = new AuthenticationService(_mockAccountService.Object, _mockPasswordHasher.Object); 28 | } 29 | 30 | [Test] 31 | public async Task Login_WithCorrectPasswordForExistingUsername_ReturnsAccountForCorrectUsername() 32 | { 33 | string expectedUsername = "testuser"; 34 | string password = "testpassword"; 35 | _mockAccountService.Setup(s => s.GetByUsername(expectedUsername)).ReturnsAsync(new Account() { AccountHolder = new User() { Username = expectedUsername } }); 36 | _mockPasswordHasher.Setup(s => s.VerifyHashedPassword(It.IsAny(), password)).Returns(PasswordVerificationResult.Success); 37 | 38 | Account account = await _authenticationService.Login(expectedUsername, password); 39 | 40 | string actualUsername = account.AccountHolder.Username; 41 | Assert.AreEqual(expectedUsername, actualUsername); 42 | } 43 | 44 | [Test] 45 | public void Login_WithIncorrectPasswordForExistingUsername_ThrowsInvalidPasswordExceptionForUsername() 46 | { 47 | string expectedUsername = "testuser"; 48 | string password = "testpassword"; 49 | _mockAccountService.Setup(s => s.GetByUsername(expectedUsername)).ReturnsAsync(new Account() { AccountHolder = new User() { Username = expectedUsername } }); 50 | _mockPasswordHasher.Setup(s => s.VerifyHashedPassword(It.IsAny(), password)).Returns(PasswordVerificationResult.Failed); 51 | 52 | InvalidPasswordException exception = Assert.ThrowsAsync(() => _authenticationService.Login(expectedUsername, password)); 53 | 54 | string actualUsername = exception.Username; 55 | Assert.AreEqual(expectedUsername, actualUsername); 56 | } 57 | 58 | [Test] 59 | public void Login_WithNonExistingUsername_ThrowsInvalidPasswordExceptionForUsername() 60 | { 61 | string expectedUsername = "testuser"; 62 | string password = "testpassword"; 63 | _mockPasswordHasher.Setup(s => s.VerifyHashedPassword(It.IsAny(), password)).Returns(PasswordVerificationResult.Failed); 64 | 65 | UserNotFoundException exception = Assert.ThrowsAsync(() => _authenticationService.Login(expectedUsername, password)); 66 | 67 | string actualUsername = exception.Username; 68 | Assert.AreEqual(expectedUsername, actualUsername); 69 | } 70 | 71 | [Test] 72 | public async Task Register_WithPasswordsNotMatching_ReturnsPasswordsDoNotMatch() 73 | { 74 | string password = "testpassword"; 75 | string confirmPassword = "confirmtestpassword"; 76 | RegistrationResult expected = RegistrationResult.PasswordsDoNotMatch; 77 | 78 | RegistrationResult actual = await _authenticationService.Register(It.IsAny(), It.IsAny(), password, confirmPassword); 79 | 80 | Assert.AreEqual(expected, actual); 81 | } 82 | 83 | [Test] 84 | public async Task Register_WithAlreadyExistingEmail_ReturnsEmailAlreadyExists() 85 | { 86 | string email = "test@gmail.com"; 87 | _mockAccountService.Setup(s => s.GetByEmail(email)).ReturnsAsync(new Account()); 88 | RegistrationResult expected = RegistrationResult.EmailAlreadyExists; 89 | 90 | RegistrationResult actual = await _authenticationService.Register(email, It.IsAny(), It.IsAny(), It.IsAny()); 91 | 92 | Assert.AreEqual(expected, actual); 93 | } 94 | 95 | [Test] 96 | public async Task Register_WithAlreadyExistingUsername_ReturnsUsernameAlreadyExists() 97 | { 98 | string username = "testuser"; 99 | _mockAccountService.Setup(s => s.GetByUsername(username)).ReturnsAsync(new Account()); 100 | RegistrationResult expected = RegistrationResult.UsernameAlreadyExists; 101 | 102 | RegistrationResult actual = await _authenticationService.Register(It.IsAny(), username, It.IsAny(), It.IsAny()); 103 | 104 | Assert.AreEqual(expected, actual); 105 | } 106 | 107 | [Test] 108 | public async Task Register_WithNonExistingUserAndMatchingPasswords_ReturnsSuccess() 109 | { 110 | RegistrationResult expected = RegistrationResult.Success; 111 | 112 | RegistrationResult actual = await _authenticationService.Register(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()); 113 | 114 | Assert.AreEqual(expected, actual); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain.Tests/Services/TransactionServices/BuyStockServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using SimpleTrader.Domain.Exceptions; 4 | using SimpleTrader.Domain.Models; 5 | using SimpleTrader.Domain.Services; 6 | using SimpleTrader.Domain.Services.TransactionServices; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace SimpleTrader.Domain.Tests.Services.TransactionServices 14 | { 15 | [TestFixture] 16 | public class BuyStockServiceTests 17 | { 18 | private Mock _mockStockPriceService; 19 | private Mock> _mockAccountService; 20 | private BuyStockService _buyStockService; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mockStockPriceService = new Mock(); 26 | _mockAccountService = new Mock>(); 27 | 28 | _buyStockService = new BuyStockService(_mockStockPriceService.Object, _mockAccountService.Object); 29 | } 30 | 31 | [Test] 32 | public void BuyStock_WithInvalidSymbol_ThrowsInvalidSymbolExceptionForSymbol() 33 | { 34 | string expectedInvalidSymbol = "bad_symbol"; 35 | _mockStockPriceService.Setup(s => s.GetPrice(expectedInvalidSymbol)).ThrowsAsync(new InvalidSymbolException(expectedInvalidSymbol)); 36 | 37 | InvalidSymbolException excpetion = Assert.ThrowsAsync( 38 | () => _buyStockService.BuyStock(It.IsAny(), expectedInvalidSymbol, It.IsAny())); 39 | string actualInvalidSymbol = excpetion.Symbol; 40 | 41 | Assert.AreEqual(expectedInvalidSymbol, actualInvalidSymbol); 42 | } 43 | 44 | [Test] 45 | public void BuyStock_WithGetPriceFailure_ThrowsException() 46 | { 47 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ThrowsAsync(new Exception()); 48 | 49 | Assert.ThrowsAsync( 50 | () => _buyStockService.BuyStock(It.IsAny(), It.IsAny(), It.IsAny())); 51 | } 52 | 53 | [Test] 54 | public void BuyStock_WithInsufficientFunds_ThrowsInsufficientFundsExceptionForBalances() 55 | { 56 | double expectedAccountBalance = 0; 57 | double expectedRequiredBalance = 100; 58 | Account buyer = CreateAccount(expectedAccountBalance); 59 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ReturnsAsync(expectedRequiredBalance); 60 | 61 | InsufficientFundsException exception = Assert.ThrowsAsync( 62 | () => _buyStockService.BuyStock(buyer, It.IsAny(), 1)); 63 | double actualAccountBalance = exception.AccountBalance; 64 | double actualRequiredBalance = exception.RequiredBalance; 65 | 66 | Assert.AreEqual(expectedAccountBalance, actualAccountBalance); 67 | Assert.AreEqual(expectedRequiredBalance, actualRequiredBalance); 68 | } 69 | 70 | [Test] 71 | public void BuyStock_WithAccountUpdateFailure_ThrowsException() 72 | { 73 | Account buyer = CreateAccount(1000); 74 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ReturnsAsync(100); 75 | _mockAccountService.Setup(s => s.Update(It.IsAny(), It.IsAny())).Throws(new Exception()); 76 | 77 | Assert.ThrowsAsync(() => _buyStockService.BuyStock(buyer, It.IsAny(), 1)); 78 | } 79 | 80 | [Test] 81 | public async Task BuyStock_WithSuccessfulPurchase_ReturnsAccountWithNewTransaction() 82 | { 83 | int expectedTransactionCount = 1; 84 | Account buyer = CreateAccount(1000); 85 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ReturnsAsync(100); 86 | 87 | buyer = await _buyStockService.BuyStock(buyer, It.IsAny(), 1); 88 | int actualTransactionCount = buyer.AssetTransactions.Count(); 89 | 90 | Assert.AreEqual(expectedTransactionCount, actualTransactionCount); 91 | } 92 | 93 | [Test] 94 | public async Task BuyStock_WithSuccessfulPurchase_ReturnsAccountWithNewBalance() 95 | { 96 | double expectedBalance = 0; 97 | Account buyer = CreateAccount(100); 98 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ReturnsAsync(50); 99 | 100 | buyer = await _buyStockService.BuyStock(buyer, It.IsAny(), 2); 101 | double actualBalance = buyer.Balance; 102 | 103 | Assert.AreEqual(expectedBalance, actualBalance); 104 | } 105 | 106 | private Account CreateAccount(double balance) 107 | { 108 | return new Account() 109 | { 110 | Balance = balance, 111 | AssetTransactions = new List() 112 | }; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain.Tests/Services/TransactionServices/SellStockServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using SimpleTrader.Domain.Exceptions; 4 | using SimpleTrader.Domain.Models; 5 | using SimpleTrader.Domain.Services; 6 | using SimpleTrader.Domain.Services.TransactionServices; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace SimpleTrader.Domain.Tests.Services.TransactionServices 13 | { 14 | [TestFixture] 15 | public class SellStockServiceTests 16 | { 17 | private SellStockService _sellStockService; 18 | 19 | private Mock _mockStockPriceService; 20 | private Mock> _mockAccountService; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mockStockPriceService = new Mock(); 26 | _mockAccountService = new Mock>(); 27 | 28 | _sellStockService = new SellStockService(_mockStockPriceService.Object, _mockAccountService.Object); 29 | } 30 | 31 | [Test] 32 | public void SellStock_WithInsufficientShares_ThrowsInsufficientSharesException() 33 | { 34 | string expectedSymbol = "T"; 35 | int expectedAccountShares = 0; 36 | int expectedRequiredShares = 10; 37 | Account seller = CreateAccount(expectedSymbol, expectedAccountShares); 38 | 39 | InsufficientSharesException exception = Assert.ThrowsAsync( 40 | () => _sellStockService.SellStock(seller, expectedSymbol, expectedRequiredShares)); 41 | string actualSymbol = exception.Symbol; 42 | double actualAccountBalance = exception.AccountShares; 43 | double actualRequiredBalance = exception.RequiredShares; 44 | 45 | Assert.AreEqual(expectedSymbol, actualSymbol); 46 | Assert.AreEqual(expectedAccountShares, actualAccountBalance); 47 | Assert.AreEqual(expectedRequiredShares, actualRequiredBalance); 48 | } 49 | 50 | [Test] 51 | public void SellStock_WithInvalidSymbol_ThrowsInvalidSymbolExceptionForSymbol() 52 | { 53 | string expectedInvalidSymbol = "bad_symbol"; 54 | Account seller = CreateAccount(expectedInvalidSymbol, 10); 55 | _mockStockPriceService.Setup(s => s.GetPrice(expectedInvalidSymbol)).ThrowsAsync(new InvalidSymbolException(expectedInvalidSymbol)); 56 | 57 | InvalidSymbolException exception = Assert.ThrowsAsync(() => _sellStockService.SellStock(seller, expectedInvalidSymbol, 5)); 58 | string actualInvalidSymbol = exception.Symbol; 59 | 60 | Assert.AreEqual(expectedInvalidSymbol, actualInvalidSymbol); 61 | } 62 | 63 | [Test] 64 | public void SellStock_WithGetPriceFailure_ThrowsException() 65 | { 66 | Account seller = CreateAccount(It.IsAny(), 10); 67 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ThrowsAsync(new Exception()); 68 | 69 | Assert.ThrowsAsync(() => _sellStockService.SellStock(seller, It.IsAny(), 5)); 70 | } 71 | 72 | [Test] 73 | public void SellStock_WithAccountUpdateFailure_ThrowsException() 74 | { 75 | Account seller = CreateAccount(It.IsAny(), 10); 76 | _mockAccountService.Setup(s => s.Update(It.IsAny(), It.IsAny())).ThrowsAsync(new Exception()); 77 | 78 | Assert.ThrowsAsync(() => _sellStockService.SellStock(seller, It.IsAny(), 5)); 79 | } 80 | 81 | [Test] 82 | public async Task SellStock_WithSuccessfulSell_ReturnsAccountWithNewTransaction() 83 | { 84 | int expectedTransactionCount = 2; 85 | Account seller = CreateAccount(It.IsAny(), 10); 86 | 87 | seller = await _sellStockService.SellStock(seller, It.IsAny(), 5); 88 | int actualTransactionCount = seller.AssetTransactions.Count; 89 | 90 | Assert.AreEqual(expectedTransactionCount, actualTransactionCount); 91 | } 92 | 93 | [Test] 94 | public async Task SellStock_WithSuccessfulSell_ReturnsAccountWithNewBalance() 95 | { 96 | double expectedBalance = 100; 97 | Account seller = CreateAccount(It.IsAny(), 10); 98 | _mockStockPriceService.Setup(s => s.GetPrice(It.IsAny())).ReturnsAsync(50); 99 | 100 | seller = await _sellStockService.SellStock(seller, It.IsAny(), 2); 101 | double actualBalance = seller.Balance; 102 | 103 | Assert.AreEqual(expectedBalance, actualBalance); 104 | } 105 | 106 | private Account CreateAccount(string symbol, int shares) 107 | { 108 | return new Account() 109 | { 110 | AssetTransactions = new List() 111 | { 112 | new AssetTransaction() 113 | { 114 | Asset = new Asset() 115 | { 116 | Symbol = symbol 117 | }, 118 | IsPurchase = true, 119 | Shares = shares 120 | } 121 | } 122 | }; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain.Tests/SimpleTrader.Domain.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Exceptions/InsufficientFundsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.Domain.Exceptions 7 | { 8 | public class InsufficientFundsException : Exception 9 | { 10 | public double AccountBalance { get; set; } 11 | public double RequiredBalance { get; set; } 12 | 13 | public InsufficientFundsException(double accountBalance, double requiredBalance) 14 | { 15 | AccountBalance = accountBalance; 16 | RequiredBalance = requiredBalance; 17 | } 18 | 19 | public InsufficientFundsException(double accountBalance, double requiredBalance, string message) : base(message) 20 | { 21 | AccountBalance = accountBalance; 22 | RequiredBalance = requiredBalance; 23 | } 24 | 25 | public InsufficientFundsException(double accountBalance, double requiredBalance, string message, Exception innerException) : base(message, innerException) 26 | { 27 | AccountBalance = accountBalance; 28 | RequiredBalance = requiredBalance; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Exceptions/InsufficientSharesException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.Domain.Exceptions 7 | { 8 | public class InsufficientSharesException : Exception 9 | { 10 | public string Symbol { get; } 11 | public int AccountShares { get; } 12 | public int RequiredShares { get; } 13 | 14 | public InsufficientSharesException(string symbol, int accountShares, int requiredShares) 15 | { 16 | Symbol = symbol; 17 | AccountShares = accountShares; 18 | RequiredShares = requiredShares; 19 | } 20 | 21 | public InsufficientSharesException(string symbol, int accountShares, int requiredShares, string message) : base(message) 22 | { 23 | Symbol = symbol; 24 | AccountShares = accountShares; 25 | RequiredShares = requiredShares; 26 | } 27 | 28 | public InsufficientSharesException(string symbol, int accountShares, int requiredShares, string message, Exception innerException) : base(message, innerException) 29 | { 30 | Symbol = symbol; 31 | AccountShares = accountShares; 32 | RequiredShares = requiredShares; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Exceptions/InvalidPasswordException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.Domain.Exceptions 7 | { 8 | public class InvalidPasswordException : Exception 9 | { 10 | public string Username { get; set; } 11 | public string Password { get; set; } 12 | 13 | public InvalidPasswordException(string username, string password) 14 | { 15 | Username = username; 16 | Password = password; 17 | } 18 | 19 | public InvalidPasswordException(string message, string username, string password) : base(message) 20 | { 21 | Username = username; 22 | Password = password; 23 | } 24 | 25 | public InvalidPasswordException(string message, Exception innerException, string username, string password) : base(message, innerException) 26 | { 27 | Username = username; 28 | Password = password; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Exceptions/InvalidSymbolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.Domain.Exceptions 7 | { 8 | public class InvalidSymbolException : Exception 9 | { 10 | public string Symbol { get; set; } 11 | 12 | public InvalidSymbolException(string symbol) 13 | { 14 | Symbol = symbol; 15 | } 16 | 17 | public InvalidSymbolException(string symbol ,string message) : base(message) 18 | { 19 | Symbol = symbol; 20 | } 21 | 22 | public InvalidSymbolException(string symbol, string message, Exception innerException) : base(message, innerException) 23 | { 24 | Symbol = symbol; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Exceptions/UserNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.Domain.Exceptions 7 | { 8 | public class UserNotFoundException : Exception 9 | { 10 | public string Username { get; set; } 11 | 12 | public UserNotFoundException(string username) 13 | { 14 | Username = username; 15 | } 16 | 17 | public UserNotFoundException(string message, string username) : base(message) 18 | { 19 | Username = username; 20 | } 21 | 22 | public UserNotFoundException(string message, Exception innerException, string username) : base(message, innerException) 23 | { 24 | Username = username; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/Account.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public class Account : DomainObject 8 | { 9 | public User AccountHolder { get; set; } 10 | public double Balance { get; set; } 11 | public ICollection AssetTransactions { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/Asset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public class Asset 8 | { 9 | public string Symbol { get; set; } 10 | public double PricePerShare { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/AssetTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public class AssetTransaction : DomainObject 8 | { 9 | public Account Account { get; set; } 10 | public bool IsPurchase { get; set; } 11 | public Asset Asset { get; set; } 12 | public int Shares { get; set; } 13 | public DateTime DateProcessed { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/DomainObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public class DomainObject 8 | { 9 | public int Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/MajorIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public enum MajorIndexType 8 | { 9 | DowJones, 10 | Nasdaq, 11 | SP500 12 | } 13 | 14 | public class MajorIndex 15 | { 16 | public string IndexName { get; set; } 17 | public double Price { get; set; } 18 | public double Changes { get; set; } 19 | public MajorIndexType Type { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.Domain.Models 6 | { 7 | public class User : DomainObject 8 | { 9 | public string Email { get; set; } 10 | public string Username { get; set; } 11 | public string PasswordHash { get; set; } 12 | public DateTime DatedJoined { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/AuthenticationServices/AuthenticationService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.Identity; 2 | using SimpleTrader.Domain.Exceptions; 3 | using SimpleTrader.Domain.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.Domain.Services.AuthenticationServices 10 | { 11 | public class AuthenticationService : IAuthenticationService 12 | { 13 | private readonly IAccountService _accountService; 14 | private readonly IPasswordHasher _passwordHasher; 15 | 16 | public AuthenticationService(IAccountService accountService, IPasswordHasher passwordHasher) 17 | { 18 | _accountService = accountService; 19 | _passwordHasher = passwordHasher; 20 | } 21 | 22 | public async Task Login(string username, string password) 23 | { 24 | Account storedAccount = await _accountService.GetByUsername(username); 25 | 26 | if(storedAccount == null) 27 | { 28 | throw new UserNotFoundException(username); 29 | } 30 | 31 | PasswordVerificationResult passwordResult = _passwordHasher.VerifyHashedPassword(storedAccount.AccountHolder.PasswordHash, password); 32 | 33 | if(passwordResult != PasswordVerificationResult.Success) 34 | { 35 | throw new InvalidPasswordException(username, password); 36 | } 37 | 38 | return storedAccount; 39 | } 40 | 41 | public async Task Register(string email, string username, string password, string confirmPassword) 42 | { 43 | RegistrationResult result = RegistrationResult.Success; 44 | 45 | if(password != confirmPassword) 46 | { 47 | result = RegistrationResult.PasswordsDoNotMatch; 48 | } 49 | 50 | Account emailAccount = await _accountService.GetByEmail(email); 51 | if(emailAccount != null) 52 | { 53 | result = RegistrationResult.EmailAlreadyExists; 54 | } 55 | 56 | Account usernameAccount = await _accountService.GetByUsername(username); 57 | if (usernameAccount != null) 58 | { 59 | result = RegistrationResult.UsernameAlreadyExists; 60 | } 61 | 62 | if(result == RegistrationResult.Success) 63 | { 64 | string hashedPassword = _passwordHasher.HashPassword(password); 65 | 66 | User user = new User() 67 | { 68 | Email = email, 69 | Username = username, 70 | PasswordHash = hashedPassword, 71 | DatedJoined = DateTime.Now 72 | }; 73 | 74 | Account account = new Account() 75 | { 76 | AccountHolder = user, 77 | Balance = 500 78 | }; 79 | 80 | await _accountService.Create(account); 81 | } 82 | 83 | return result; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/AuthenticationServices/IAuthenticationService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Exceptions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SimpleTrader.Domain.Services.AuthenticationServices 9 | { 10 | public enum RegistrationResult 11 | { 12 | Success, 13 | PasswordsDoNotMatch, 14 | EmailAlreadyExists, 15 | UsernameAlreadyExists 16 | } 17 | 18 | public interface IAuthenticationService 19 | { 20 | /// 21 | /// Register a new user. 22 | /// 23 | /// The user's email. 24 | /// The user's name. 25 | /// The user's password. 26 | /// The user's confirmed password. 27 | /// The result of the registration. 28 | /// Thrown if the registration fails. 29 | Task Register(string email, string username, string password, string confirmPassword); 30 | 31 | /// 32 | /// Get an account for a user's credentials. 33 | /// 34 | /// The user's name. 35 | /// The user's password. 36 | /// The account for the user. 37 | /// Thrown if the user does not exist. 38 | /// Thrown if the password is invalid. 39 | /// Thrown if the login fails. 40 | Task Login(string username, string password); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/IAccountService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SimpleTrader.Domain.Services 8 | { 9 | public interface IAccountService : IDataService 10 | { 11 | Task GetByUsername(string username); 12 | Task GetByEmail(string email); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/IDataService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace SimpleTrader.Domain.Services 7 | { 8 | public interface IDataService 9 | { 10 | Task> GetAll(); 11 | 12 | Task Get(int id); 13 | 14 | Task Create(T entity); 15 | 16 | Task Update(int id, T entity); 17 | 18 | Task Delete(int id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/IMajorIndexService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SimpleTrader.Domain.Services 8 | { 9 | public interface IMajorIndexService 10 | { 11 | Task GetMajorIndex(MajorIndexType indexType); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/IStockPriceService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using SimpleTrader.Domain.Exceptions; 5 | using System.Threading.Tasks; 6 | 7 | namespace SimpleTrader.Domain.Services 8 | { 9 | public interface IStockPriceService 10 | { 11 | /// 12 | /// Get the share price for a symbol. 13 | /// 14 | /// The symbol to get the price of. 15 | /// The price of symbol. 16 | /// Thrown if symbol does not exist. 17 | /// Thrown if getting the symbol fails. 18 | Task GetPrice(string symbol); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/TransactionServices/BuyStockService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SimpleTrader.Domain.Services.TransactionServices 9 | { 10 | public class BuyStockService : IBuyStockService 11 | { 12 | private readonly IStockPriceService _stockPriceService; 13 | private readonly IDataService _accountService; 14 | 15 | public BuyStockService(IStockPriceService stockPriceService, IDataService accountService) 16 | { 17 | _stockPriceService = stockPriceService; 18 | _accountService = accountService; 19 | } 20 | 21 | public async Task BuyStock(Account buyer, string symbol, int shares) 22 | { 23 | double stockPrice = await _stockPriceService.GetPrice(symbol); 24 | 25 | double transactionPrice = stockPrice * shares; 26 | 27 | if (transactionPrice > buyer.Balance) 28 | { 29 | throw new InsufficientFundsException(buyer.Balance, transactionPrice); 30 | } 31 | 32 | AssetTransaction transaction = new AssetTransaction() 33 | { 34 | Account = buyer, 35 | Asset = new Asset() 36 | { 37 | PricePerShare = stockPrice, 38 | Symbol = symbol 39 | }, 40 | DateProcessed = DateTime.Now, 41 | Shares = shares, 42 | IsPurchase = true 43 | }; 44 | 45 | buyer.AssetTransactions.Add(transaction); 46 | buyer.Balance -= transactionPrice; 47 | 48 | await _accountService.Update(buyer.Id, buyer); 49 | 50 | return buyer; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/TransactionServices/IBuyStockService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Exceptions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SimpleTrader.Domain.Services.TransactionServices 9 | { 10 | public interface IBuyStockService 11 | { 12 | /// 13 | /// Purchase a stock for an account. 14 | /// 15 | /// The account of the buyer. 16 | /// The symbol purchased. 17 | /// The amount of shares. 18 | /// The updated account. 19 | /// Thrown if the acccount has an insufficient balance. 20 | /// Thrown if the purchased symbol is invalid. 21 | /// Thrown if the transaction fails. 22 | Task BuyStock(Account buyer, string symbol, int shares); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/TransactionServices/ISellStockService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SimpleTrader.Domain.Services.TransactionServices 9 | { 10 | public interface ISellStockService 11 | { 12 | /// 13 | /// Sell a stock for an account. 14 | /// 15 | /// The account of the seller. 16 | /// The symbol sold. 17 | /// The amount of shares to sell. 18 | /// The updated account. 19 | /// Thrown if the seller has insufficient shares for the symbol. 20 | /// Thrown if the purchased symbol is invalid. 21 | /// Thrown if the transaction fails. 22 | Task SellStock(Account seller, string symbol, int shares); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/Services/TransactionServices/SellStockService.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.Domain.Services.TransactionServices 10 | { 11 | public class SellStockService : ISellStockService 12 | { 13 | private readonly IStockPriceService _stockPriceService; 14 | private readonly IDataService _accountService; 15 | 16 | public SellStockService(IStockPriceService stockPriceService, IDataService accountService) 17 | { 18 | _stockPriceService = stockPriceService; 19 | _accountService = accountService; 20 | } 21 | 22 | public async Task SellStock(Account seller, string symbol, int shares) 23 | { 24 | // Validate seller has sufficient shares. 25 | int accountShares = GetAccountSharesForSymbol(seller, symbol); 26 | if(accountShares < shares) 27 | { 28 | throw new InsufficientSharesException(symbol, accountShares, shares); 29 | } 30 | 31 | double stockPrice = await _stockPriceService.GetPrice(symbol); 32 | 33 | seller.AssetTransactions.Add(new AssetTransaction() 34 | { 35 | Account = seller, 36 | Asset = new Asset() 37 | { 38 | PricePerShare = stockPrice, 39 | Symbol = symbol 40 | }, 41 | DateProcessed = DateTime.Now, 42 | IsPurchase = false, 43 | Shares = shares 44 | }); 45 | 46 | seller.Balance += stockPrice * shares; 47 | 48 | await _accountService.Update(seller.Id, seller); 49 | 50 | return seller; 51 | } 52 | 53 | private int GetAccountSharesForSymbol(Account seller, string symbol) 54 | { 55 | IEnumerable accountTransactionsForSymbol = seller.AssetTransactions.Where(a => a.Asset.Symbol == symbol); 56 | 57 | return accountTransactionsForSymbol.Sum(a => a.IsPurchase ? a.Shares : -a.Shares); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.Domain/SimpleTrader.Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20191128145049_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using SimpleTrader.EntityFramework; 9 | 10 | namespace SimpleTrader.EntityFramework.Migrations 11 | { 12 | [DbContext(typeof(SimpleTraderDbContext))] 13 | [Migration("20191128145049_initial")] 14 | partial class initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("AccountHolderId"); 31 | 32 | b.Property("Balance"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("AccountHolderId"); 37 | 38 | b.ToTable("Accounts"); 39 | }); 40 | 41 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 42 | { 43 | b.Property("Id") 44 | .ValueGeneratedOnAdd() 45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 46 | 47 | b.Property("AccountId"); 48 | 49 | b.Property("DateProcessed"); 50 | 51 | b.Property("IsPurchase"); 52 | 53 | b.Property("Shares"); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("AccountId"); 58 | 59 | b.ToTable("AssetTransactions"); 60 | }); 61 | 62 | modelBuilder.Entity("SimpleTrader.Domain.Models.User", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd() 66 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 67 | 68 | b.Property("DatedJoined"); 69 | 70 | b.Property("Email"); 71 | 72 | b.Property("Password"); 73 | 74 | b.Property("Username"); 75 | 76 | b.HasKey("Id"); 77 | 78 | b.ToTable("Users"); 79 | }); 80 | 81 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 82 | { 83 | b.HasOne("SimpleTrader.Domain.Models.User", "AccountHolder") 84 | .WithMany() 85 | .HasForeignKey("AccountHolderId"); 86 | }); 87 | 88 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 89 | { 90 | b.HasOne("SimpleTrader.Domain.Models.Account", "Account") 91 | .WithMany("AssetTransactions") 92 | .HasForeignKey("AccountId"); 93 | 94 | b.OwnsOne("SimpleTrader.Domain.Models.Stock", "Stock", b1 => 95 | { 96 | b1.Property("AssetTransactionId") 97 | .ValueGeneratedOnAdd() 98 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 99 | 100 | b1.Property("PricePerShare"); 101 | 102 | b1.Property("Symbol"); 103 | 104 | b1.HasKey("AssetTransactionId"); 105 | 106 | b1.ToTable("AssetTransactions"); 107 | 108 | b1.HasOne("SimpleTrader.Domain.Models.AssetTransaction") 109 | .WithOne("Stock") 110 | .HasForeignKey("SimpleTrader.Domain.Models.Stock", "AssetTransactionId") 111 | .OnDelete(DeleteBehavior.Cascade); 112 | }); 113 | }); 114 | #pragma warning restore 612, 618 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20191128145049_initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace SimpleTrader.EntityFramework.Migrations 6 | { 7 | public partial class initial : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Users", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false) 16 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 17 | Email = table.Column(nullable: true), 18 | Username = table.Column(nullable: true), 19 | Password = table.Column(nullable: true), 20 | DatedJoined = table.Column(nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Users", x => x.Id); 25 | }); 26 | 27 | migrationBuilder.CreateTable( 28 | name: "Accounts", 29 | columns: table => new 30 | { 31 | Id = table.Column(nullable: false) 32 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 33 | AccountHolderId = table.Column(nullable: true), 34 | Balance = table.Column(nullable: false) 35 | }, 36 | constraints: table => 37 | { 38 | table.PrimaryKey("PK_Accounts", x => x.Id); 39 | table.ForeignKey( 40 | name: "FK_Accounts_Users_AccountHolderId", 41 | column: x => x.AccountHolderId, 42 | principalTable: "Users", 43 | principalColumn: "Id", 44 | onDelete: ReferentialAction.Restrict); 45 | }); 46 | 47 | migrationBuilder.CreateTable( 48 | name: "AssetTransactions", 49 | columns: table => new 50 | { 51 | Id = table.Column(nullable: false) 52 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 53 | AccountId = table.Column(nullable: true), 54 | IsPurchase = table.Column(nullable: false), 55 | Stock_Symbol = table.Column(nullable: true), 56 | Stock_PricePerShare = table.Column(nullable: false), 57 | Shares = table.Column(nullable: false), 58 | DateProcessed = table.Column(nullable: false) 59 | }, 60 | constraints: table => 61 | { 62 | table.PrimaryKey("PK_AssetTransactions", x => x.Id); 63 | table.ForeignKey( 64 | name: "FK_AssetTransactions_Accounts_AccountId", 65 | column: x => x.AccountId, 66 | principalTable: "Accounts", 67 | principalColumn: "Id", 68 | onDelete: ReferentialAction.Restrict); 69 | }); 70 | 71 | migrationBuilder.CreateIndex( 72 | name: "IX_Accounts_AccountHolderId", 73 | table: "Accounts", 74 | column: "AccountHolderId"); 75 | 76 | migrationBuilder.CreateIndex( 77 | name: "IX_AssetTransactions_AccountId", 78 | table: "AssetTransactions", 79 | column: "AccountId"); 80 | } 81 | 82 | protected override void Down(MigrationBuilder migrationBuilder) 83 | { 84 | migrationBuilder.DropTable( 85 | name: "AssetTransactions"); 86 | 87 | migrationBuilder.DropTable( 88 | name: "Accounts"); 89 | 90 | migrationBuilder.DropTable( 91 | name: "Users"); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20200125172134_stock-to-asset.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using SimpleTrader.EntityFramework; 9 | 10 | namespace SimpleTrader.EntityFramework.Migrations 11 | { 12 | [DbContext(typeof(SimpleTraderDbContext))] 13 | [Migration("20200125172134_stock-to-asset")] 14 | partial class stocktoasset 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("AccountHolderId"); 31 | 32 | b.Property("Balance"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("AccountHolderId"); 37 | 38 | b.ToTable("Accounts"); 39 | }); 40 | 41 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 42 | { 43 | b.Property("Id") 44 | .ValueGeneratedOnAdd() 45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 46 | 47 | b.Property("AccountId"); 48 | 49 | b.Property("DateProcessed"); 50 | 51 | b.Property("IsPurchase"); 52 | 53 | b.Property("Shares"); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("AccountId"); 58 | 59 | b.ToTable("AssetTransactions"); 60 | }); 61 | 62 | modelBuilder.Entity("SimpleTrader.Domain.Models.User", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd() 66 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 67 | 68 | b.Property("DatedJoined"); 69 | 70 | b.Property("Email"); 71 | 72 | b.Property("Password"); 73 | 74 | b.Property("Username"); 75 | 76 | b.HasKey("Id"); 77 | 78 | b.ToTable("Users"); 79 | }); 80 | 81 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 82 | { 83 | b.HasOne("SimpleTrader.Domain.Models.User", "AccountHolder") 84 | .WithMany() 85 | .HasForeignKey("AccountHolderId"); 86 | }); 87 | 88 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 89 | { 90 | b.HasOne("SimpleTrader.Domain.Models.Account", "Account") 91 | .WithMany("AssetTransactions") 92 | .HasForeignKey("AccountId"); 93 | 94 | b.OwnsOne("SimpleTrader.Domain.Models.Asset", "Asset", b1 => 95 | { 96 | b1.Property("AssetTransactionId") 97 | .ValueGeneratedOnAdd() 98 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 99 | 100 | b1.Property("PricePerShare"); 101 | 102 | b1.Property("Symbol"); 103 | 104 | b1.HasKey("AssetTransactionId"); 105 | 106 | b1.ToTable("AssetTransactions"); 107 | 108 | b1.HasOne("SimpleTrader.Domain.Models.AssetTransaction") 109 | .WithOne("Asset") 110 | .HasForeignKey("SimpleTrader.Domain.Models.Asset", "AssetTransactionId") 111 | .OnDelete(DeleteBehavior.Cascade); 112 | }); 113 | }); 114 | #pragma warning restore 612, 618 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20200125172134_stock-to-asset.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace SimpleTrader.EntityFramework.Migrations 4 | { 5 | public partial class stocktoasset : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.RenameColumn( 10 | name: "Stock_Symbol", 11 | table: "AssetTransactions", 12 | newName: "Asset_Symbol"); 13 | 14 | migrationBuilder.RenameColumn( 15 | name: "Stock_PricePerShare", 16 | table: "AssetTransactions", 17 | newName: "Asset_PricePerShare"); 18 | } 19 | 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.RenameColumn( 23 | name: "Asset_Symbol", 24 | table: "AssetTransactions", 25 | newName: "Stock_Symbol"); 26 | 27 | migrationBuilder.RenameColumn( 28 | name: "Asset_PricePerShare", 29 | table: "AssetTransactions", 30 | newName: "Stock_PricePerShare"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20200404125938_password_hash.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using SimpleTrader.EntityFramework; 9 | 10 | namespace SimpleTrader.EntityFramework.Migrations 11 | { 12 | [DbContext(typeof(SimpleTraderDbContext))] 13 | [Migration("20200404125938_password_hash")] 14 | partial class password_hash 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("AccountHolderId"); 31 | 32 | b.Property("Balance"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("AccountHolderId"); 37 | 38 | b.ToTable("Accounts"); 39 | }); 40 | 41 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 42 | { 43 | b.Property("Id") 44 | .ValueGeneratedOnAdd() 45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 46 | 47 | b.Property("AccountId"); 48 | 49 | b.Property("DateProcessed"); 50 | 51 | b.Property("IsPurchase"); 52 | 53 | b.Property("Shares"); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("AccountId"); 58 | 59 | b.ToTable("AssetTransactions"); 60 | }); 61 | 62 | modelBuilder.Entity("SimpleTrader.Domain.Models.User", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd() 66 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 67 | 68 | b.Property("DatedJoined"); 69 | 70 | b.Property("Email"); 71 | 72 | b.Property("PasswordHash"); 73 | 74 | b.Property("Username"); 75 | 76 | b.HasKey("Id"); 77 | 78 | b.ToTable("Users"); 79 | }); 80 | 81 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 82 | { 83 | b.HasOne("SimpleTrader.Domain.Models.User", "AccountHolder") 84 | .WithMany() 85 | .HasForeignKey("AccountHolderId"); 86 | }); 87 | 88 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 89 | { 90 | b.HasOne("SimpleTrader.Domain.Models.Account", "Account") 91 | .WithMany("AssetTransactions") 92 | .HasForeignKey("AccountId"); 93 | 94 | b.OwnsOne("SimpleTrader.Domain.Models.Asset", "Asset", b1 => 95 | { 96 | b1.Property("AssetTransactionId") 97 | .ValueGeneratedOnAdd() 98 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 99 | 100 | b1.Property("PricePerShare"); 101 | 102 | b1.Property("Symbol"); 103 | 104 | b1.HasKey("AssetTransactionId"); 105 | 106 | b1.ToTable("AssetTransactions"); 107 | 108 | b1.HasOne("SimpleTrader.Domain.Models.AssetTransaction") 109 | .WithOne("Asset") 110 | .HasForeignKey("SimpleTrader.Domain.Models.Asset", "AssetTransactionId") 111 | .OnDelete(DeleteBehavior.Cascade); 112 | }); 113 | }); 114 | #pragma warning restore 612, 618 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/20200404125938_password_hash.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace SimpleTrader.EntityFramework.Migrations 4 | { 5 | public partial class password_hash : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.RenameColumn( 10 | name: "Password", 11 | table: "Users", 12 | newName: "PasswordHash"); 13 | } 14 | 15 | protected override void Down(MigrationBuilder migrationBuilder) 16 | { 17 | migrationBuilder.RenameColumn( 18 | name: "PasswordHash", 19 | table: "Users", 20 | newName: "Password"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Migrations/SimpleTraderDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using SimpleTrader.EntityFramework; 8 | 9 | namespace SimpleTrader.EntityFramework.Migrations 10 | { 11 | [DbContext(typeof(SimpleTraderDbContext))] 12 | partial class SimpleTraderDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd() 26 | .HasColumnType("int") 27 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 28 | 29 | b.Property("AccountHolderId") 30 | .HasColumnType("int"); 31 | 32 | b.Property("Balance") 33 | .HasColumnType("float"); 34 | 35 | b.HasKey("Id"); 36 | 37 | b.HasIndex("AccountHolderId"); 38 | 39 | b.ToTable("Accounts"); 40 | }); 41 | 42 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 43 | { 44 | b.Property("Id") 45 | .ValueGeneratedOnAdd() 46 | .HasColumnType("int") 47 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 48 | 49 | b.Property("AccountId") 50 | .HasColumnType("int"); 51 | 52 | b.Property("DateProcessed") 53 | .HasColumnType("datetime2"); 54 | 55 | b.Property("IsPurchase") 56 | .HasColumnType("bit"); 57 | 58 | b.Property("Shares") 59 | .HasColumnType("int"); 60 | 61 | b.HasKey("Id"); 62 | 63 | b.HasIndex("AccountId"); 64 | 65 | b.ToTable("AssetTransactions"); 66 | }); 67 | 68 | modelBuilder.Entity("SimpleTrader.Domain.Models.User", b => 69 | { 70 | b.Property("Id") 71 | .ValueGeneratedOnAdd() 72 | .HasColumnType("int") 73 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 74 | 75 | b.Property("DatedJoined") 76 | .HasColumnType("datetime2"); 77 | 78 | b.Property("Email") 79 | .HasColumnType("nvarchar(max)"); 80 | 81 | b.Property("PasswordHash") 82 | .HasColumnType("nvarchar(max)"); 83 | 84 | b.Property("Username") 85 | .HasColumnType("nvarchar(max)"); 86 | 87 | b.HasKey("Id"); 88 | 89 | b.ToTable("Users"); 90 | }); 91 | 92 | modelBuilder.Entity("SimpleTrader.Domain.Models.Account", b => 93 | { 94 | b.HasOne("SimpleTrader.Domain.Models.User", "AccountHolder") 95 | .WithMany() 96 | .HasForeignKey("AccountHolderId"); 97 | }); 98 | 99 | modelBuilder.Entity("SimpleTrader.Domain.Models.AssetTransaction", b => 100 | { 101 | b.HasOne("SimpleTrader.Domain.Models.Account", "Account") 102 | .WithMany("AssetTransactions") 103 | .HasForeignKey("AccountId"); 104 | 105 | b.OwnsOne("SimpleTrader.Domain.Models.Asset", "Asset", b1 => 106 | { 107 | b1.Property("AssetTransactionId") 108 | .ValueGeneratedOnAdd() 109 | .HasColumnType("int") 110 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 111 | 112 | b1.Property("PricePerShare") 113 | .HasColumnType("float"); 114 | 115 | b1.Property("Symbol") 116 | .HasColumnType("nvarchar(max)"); 117 | 118 | b1.HasKey("AssetTransactionId"); 119 | 120 | b1.ToTable("AssetTransactions"); 121 | 122 | b1.WithOwner() 123 | .HasForeignKey("AssetTransactionId"); 124 | }); 125 | }); 126 | #pragma warning restore 612, 618 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Services/AccountDataService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using SimpleTrader.Domain.Models; 4 | using SimpleTrader.Domain.Services; 5 | using SimpleTrader.EntityFramework.Services.Common; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleTrader.EntityFramework.Services 12 | { 13 | public class AccountDataService : IAccountService 14 | { 15 | private readonly SimpleTraderDbContextFactory _contextFactory; 16 | private readonly NonQueryDataService _nonQueryDataService; 17 | 18 | public AccountDataService(SimpleTraderDbContextFactory contextFactory) 19 | { 20 | _contextFactory = contextFactory; 21 | _nonQueryDataService = new NonQueryDataService(contextFactory); 22 | } 23 | 24 | public async Task Create(Account entity) 25 | { 26 | return await _nonQueryDataService.Create(entity); 27 | } 28 | 29 | public async Task Delete(int id) 30 | { 31 | return await _nonQueryDataService.Delete(id); 32 | } 33 | 34 | public async Task Get(int id) 35 | { 36 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 37 | { 38 | Account entity = await context.Accounts 39 | .Include(a => a.AccountHolder) 40 | .Include(a => a.AssetTransactions) 41 | .FirstOrDefaultAsync((e) => e.Id == id); 42 | return entity; 43 | } 44 | } 45 | 46 | public async Task> GetAll() 47 | { 48 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 49 | { 50 | IEnumerable entities = await context.Accounts 51 | .Include(a => a.AccountHolder) 52 | .Include(a => a.AssetTransactions) 53 | .ToListAsync(); 54 | return entities; 55 | } 56 | } 57 | 58 | public async Task GetByEmail(string email) 59 | { 60 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 61 | { 62 | return await context.Accounts 63 | .Include(a => a.AccountHolder) 64 | .Include(a => a.AssetTransactions) 65 | .FirstOrDefaultAsync(a => a.AccountHolder.Email == email); 66 | } 67 | } 68 | 69 | public async Task GetByUsername(string username) 70 | { 71 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 72 | { 73 | return await context.Accounts 74 | .Include(a => a.AccountHolder) 75 | .Include(a => a.AssetTransactions) 76 | .FirstOrDefaultAsync(a => a.AccountHolder.Username == username); 77 | } 78 | } 79 | 80 | public async Task Update(int id, Account entity) 81 | { 82 | return await _nonQueryDataService.Update(id, entity); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Services/Common/NonQueryDataService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using SimpleTrader.Domain.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.EntityFramework.Services.Common 10 | { 11 | public class NonQueryDataService where T : DomainObject 12 | { 13 | private readonly SimpleTraderDbContextFactory _contextFactory; 14 | 15 | public NonQueryDataService(SimpleTraderDbContextFactory contextFactory) 16 | { 17 | _contextFactory = contextFactory; 18 | } 19 | 20 | public async Task Create(T entity) 21 | { 22 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 23 | { 24 | EntityEntry createdResult = await context.Set().AddAsync(entity); 25 | await context.SaveChangesAsync(); 26 | 27 | return createdResult.Entity; 28 | } 29 | } 30 | 31 | public async Task Update(int id, T entity) 32 | { 33 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 34 | { 35 | entity.Id = id; 36 | 37 | context.Set().Update(entity); 38 | await context.SaveChangesAsync(); 39 | 40 | return entity; 41 | } 42 | } 43 | 44 | public async Task Delete(int id) 45 | { 46 | using (SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 47 | { 48 | T entity = await context.Set().FirstOrDefaultAsync((e) => e.Id == id); 49 | context.Set().Remove(entity); 50 | await context.SaveChangesAsync(); 51 | 52 | return true; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/Services/GenericDataService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using SimpleTrader.Domain.Models; 4 | using SimpleTrader.Domain.Services; 5 | using SimpleTrader.EntityFramework.Services.Common; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleTrader.EntityFramework.Services 12 | { 13 | public class GenericDataService : IDataService where T : DomainObject 14 | { 15 | private readonly SimpleTraderDbContextFactory _contextFactory; 16 | private readonly NonQueryDataService _nonQueryDataService; 17 | 18 | public GenericDataService(SimpleTraderDbContextFactory contextFactory) 19 | { 20 | _contextFactory = contextFactory; 21 | _nonQueryDataService = new NonQueryDataService(contextFactory); 22 | } 23 | 24 | public async Task Create(T entity) 25 | { 26 | return await _nonQueryDataService.Create(entity); 27 | } 28 | 29 | public async Task Delete(int id) 30 | { 31 | return await _nonQueryDataService.Delete(id); 32 | } 33 | 34 | public async Task Get(int id) 35 | { 36 | using(SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 37 | { 38 | T entity = await context.Set().FirstOrDefaultAsync((e) => e.Id == id); 39 | return entity; 40 | } 41 | } 42 | 43 | public async Task> GetAll() 44 | { 45 | using(SimpleTraderDbContext context = _contextFactory.CreateDbContext()) 46 | { 47 | IEnumerable entities = await context.Set().ToListAsync(); 48 | return entities; 49 | } 50 | } 51 | 52 | public async Task Update(int id, T entity) 53 | { 54 | return await _nonQueryDataService.Update(id, entity); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/SimpleTrader.EntityFramework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/SimpleTraderDbContext.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.EntityFrameworkCore; 3 | using SimpleTrader.Domain.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace SimpleTrader.EntityFramework 9 | { 10 | public class SimpleTraderDbContext : DbContext 11 | { 12 | public DbSet Users { get; set; } 13 | public DbSet Accounts { get; set; } 14 | public DbSet AssetTransactions { get; set; } 15 | public SimpleTraderDbContext(DbContextOptions options) : base(options) { } 16 | 17 | protected override void OnModelCreating(ModelBuilder modelBuilder) 18 | { 19 | modelBuilder.Entity().OwnsOne(a => a.Asset); 20 | 21 | base.OnModelCreating(modelBuilder); 22 | } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.EntityFramework/SimpleTraderDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Design; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace SimpleTrader.EntityFramework 8 | { 9 | public class SimpleTraderDbContextFactory 10 | { 11 | private readonly Action _configureDbContext; 12 | 13 | public SimpleTraderDbContextFactory(Action configureDbContext) 14 | { 15 | _configureDbContext = configureDbContext; 16 | } 17 | 18 | public SimpleTraderDbContext CreateDbContext() 19 | { 20 | DbContextOptionsBuilder options = new DbContextOptionsBuilder(); 21 | 22 | _configureDbContext(options); 23 | 24 | return new SimpleTraderDbContext(options.Options); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/FinancialModelingPrepHttpClient.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SimpleTrader.FinancialModelingPrepAPI.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.FinancialModelingPrepAPI 10 | { 11 | public class FinancialModelingPrepHttpClient 12 | { 13 | private readonly HttpClient _client; 14 | private readonly string _apiKey; 15 | 16 | public FinancialModelingPrepHttpClient(HttpClient client, FinancialModelingPrepAPIKey apiKey) 17 | { 18 | _client = client; 19 | _apiKey = apiKey.Key; 20 | } 21 | 22 | public async Task GetAsync(string uri) 23 | { 24 | HttpResponseMessage response = await _client.GetAsync($"{uri}?apikey={_apiKey}"); 25 | string jsonResponse = await response.Content.ReadAsStringAsync(); 26 | 27 | return JsonConvert.DeserializeObject(jsonResponse); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/Models/FinancialModelingPrepAPIKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.FinancialModelingPrepAPI.Models 6 | { 7 | public class FinancialModelingPrepAPIKey 8 | { 9 | public string Key { get; } 10 | 11 | public FinancialModelingPrepAPIKey(string key) 12 | { 13 | Key = key; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/Results/StockPriceResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.FinancialModelingPrepAPI.Results 6 | { 7 | public class StockPriceResult 8 | { 9 | public double Price { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/Services/MajorIndexService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SimpleTrader.Domain.Models; 3 | using SimpleTrader.Domain.Services; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SimpleTrader.FinancialModelingPrepAPI.Services 11 | { 12 | public class MajorIndexService : IMajorIndexService 13 | { 14 | private readonly FinancialModelingPrepHttpClient _client; 15 | 16 | public MajorIndexService(FinancialModelingPrepHttpClient client) 17 | { 18 | _client = client; 19 | } 20 | 21 | public async Task GetMajorIndex(MajorIndexType indexType) 22 | { 23 | string uri = "majors-indexes/" + GetUriSuffix(indexType); 24 | 25 | MajorIndex majorIndex = await _client.GetAsync(uri); 26 | majorIndex.Type = indexType; 27 | 28 | return majorIndex; 29 | } 30 | 31 | private string GetUriSuffix(MajorIndexType indexType) 32 | { 33 | switch(indexType) 34 | { 35 | case MajorIndexType.DowJones: 36 | return ".DJI"; 37 | case MajorIndexType.Nasdaq: 38 | return ".IXIC"; 39 | case MajorIndexType.SP500: 40 | return ".INX"; 41 | default: 42 | throw new Exception("MajorIndexType does not have a suffix defined."); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/Services/StockPriceService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SimpleTrader.Domain.Exceptions; 3 | using SimpleTrader.Domain.Services; 4 | using SimpleTrader.FinancialModelingPrepAPI.Results; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleTrader.FinancialModelingPrepAPI.Services 12 | { 13 | public class StockPriceService : IStockPriceService 14 | { 15 | private readonly FinancialModelingPrepHttpClient _client; 16 | 17 | public StockPriceService(FinancialModelingPrepHttpClient client) 18 | { 19 | _client = client; 20 | } 21 | 22 | public async Task GetPrice(string symbol) 23 | { 24 | string uri = "stock/real-time-price/" + symbol; 25 | 26 | StockPriceResult stockPriceResult = await _client.GetAsync(uri); 27 | 28 | if(stockPriceResult.Price == 0) 29 | { 30 | throw new InvalidSymbolException(symbol); 31 | } 32 | 33 | return stockPriceResult.Price; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.FinancialModelingPrepAPI/SimpleTrader.FinancialModelingPrepAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/App.xaml: -------------------------------------------------------------------------------- 1 |  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 | 43 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using SimpleTrader.EntityFramework; 6 | using SimpleTrader.WPF.HostBuilders; 7 | using System.Windows; 8 | 9 | namespace SimpleTrader.WPF 10 | { 11 | public partial class App : Application 12 | { 13 | private readonly IHost _host; 14 | 15 | public App() 16 | { 17 | _host = CreateHostBuilder().Build(); 18 | } 19 | 20 | public static IHostBuilder CreateHostBuilder(string[] args = null) 21 | { 22 | return Host.CreateDefaultBuilder(args) 23 | .AddConfiguration() 24 | .AddFinanceAPI() 25 | .AddDbContext() 26 | .AddServices() 27 | .AddStores() 28 | .AddViewModels() 29 | .AddViews(); 30 | } 31 | 32 | protected override void OnStartup(StartupEventArgs e) 33 | { 34 | _host.Start(); 35 | 36 | SimpleTraderDbContextFactory contextFactory = _host.Services.GetRequiredService(); 37 | using(SimpleTraderDbContext context = contextFactory.CreateDbContext()) 38 | { 39 | context.Database.Migrate(); 40 | } 41 | 42 | Window window = _host.Services.GetRequiredService(); 43 | window.Show(); 44 | 45 | base.OnStartup(e); 46 | } 47 | 48 | protected override async void OnExit(ExitEventArgs e) 49 | { 50 | await _host.StopAsync(); 51 | _host.Dispose(); 52 | 53 | base.OnExit(e); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/AsyncCommandBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using System.Windows.Input; 6 | 7 | namespace SimpleTrader.WPF.Commands 8 | { 9 | public abstract class AsyncCommandBase : ICommand 10 | { 11 | private bool _isExecuting; 12 | public bool IsExecuting 13 | { 14 | get 15 | { 16 | return _isExecuting; 17 | } 18 | set 19 | { 20 | _isExecuting = value; 21 | OnCanExecuteChanged(); 22 | } 23 | } 24 | 25 | public event EventHandler CanExecuteChanged; 26 | 27 | public virtual bool CanExecute(object parameter) 28 | { 29 | return !IsExecuting; 30 | } 31 | 32 | public async void Execute(object parameter) 33 | { 34 | IsExecuting = true; 35 | 36 | await ExecuteAsync(parameter); 37 | 38 | IsExecuting = false; 39 | } 40 | 41 | public abstract Task ExecuteAsync(object parameter); 42 | 43 | protected void OnCanExecuteChanged() 44 | { 45 | CanExecuteChanged?.Invoke(this, new EventArgs()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/BuyStockCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Models; 3 | using SimpleTrader.Domain.Services.TransactionServices; 4 | using SimpleTrader.WPF.State.Accounts; 5 | using SimpleTrader.WPF.ViewModels; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows; 12 | using System.Windows.Input; 13 | 14 | namespace SimpleTrader.WPF.Commands 15 | { 16 | public class BuyStockCommand : AsyncCommandBase 17 | { 18 | private readonly BuyViewModel _buyViewModel; 19 | private readonly IBuyStockService _buyStockService; 20 | private readonly IAccountStore _accountStore; 21 | 22 | public BuyStockCommand(BuyViewModel buyViewModel, IBuyStockService buyStockService, IAccountStore accountStore) 23 | { 24 | _buyViewModel = buyViewModel; 25 | _buyStockService = buyStockService; 26 | _accountStore = accountStore; 27 | 28 | _buyViewModel.PropertyChanged += BuyViewModel_PropertyChanged; 29 | } 30 | 31 | public override bool CanExecute(object parameter) 32 | { 33 | return _buyViewModel.CanBuyStock && base.CanExecute(parameter); 34 | } 35 | 36 | public override async Task ExecuteAsync(object parameter) 37 | { 38 | _buyViewModel.StatusMessage = string.Empty; 39 | _buyViewModel.ErrorMessage = string.Empty; 40 | 41 | try 42 | { 43 | string symbol = _buyViewModel.Symbol; 44 | int shares = _buyViewModel.SharesToBuy; 45 | Account account = await _buyStockService.BuyStock(_accountStore.CurrentAccount, symbol, shares); 46 | 47 | _accountStore.CurrentAccount = account; 48 | 49 | _buyViewModel.StatusMessage = $"Successfully purchased {shares} shares of {symbol}."; 50 | } 51 | catch(InsufficientFundsException) 52 | { 53 | _buyViewModel.ErrorMessage = "Account has insufficient funds. Please transfer more money into your account."; 54 | } 55 | catch(InvalidSymbolException) 56 | { 57 | _buyViewModel.ErrorMessage = "Symbol does not exist."; 58 | } 59 | catch (Exception) 60 | { 61 | _buyViewModel.ErrorMessage = "Transaction failed."; 62 | } 63 | } 64 | 65 | private void BuyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 66 | { 67 | if (e.PropertyName == nameof(BuyViewModel.CanBuyStock)) 68 | { 69 | OnCanExecuteChanged(); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/LoadMajorIndexesCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Services; 3 | using SimpleTrader.WPF.ViewModels; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SimpleTrader.WPF.Commands 11 | { 12 | public class LoadMajorIndexesCommand : AsyncCommandBase 13 | { 14 | private readonly MajorIndexListingViewModel _majorIndexListingViewModel; 15 | private readonly IMajorIndexService _majorIndexService; 16 | 17 | public LoadMajorIndexesCommand(MajorIndexListingViewModel majorIndexListingViewModel, IMajorIndexService majorIndexService) 18 | { 19 | _majorIndexListingViewModel = majorIndexListingViewModel; 20 | _majorIndexService = majorIndexService; 21 | } 22 | 23 | public override async Task ExecuteAsync(object parameter) 24 | { 25 | _majorIndexListingViewModel.IsLoading = true; 26 | 27 | await Task.WhenAll(LoadDowJones(), LoadNasdaq(), LoadSP500()); 28 | 29 | _majorIndexListingViewModel.IsLoading = false; 30 | } 31 | 32 | private async Task LoadDowJones() 33 | { 34 | _majorIndexListingViewModel.DowJones = await _majorIndexService.GetMajorIndex(MajorIndexType.DowJones); 35 | } 36 | 37 | private async Task LoadNasdaq() 38 | { 39 | _majorIndexListingViewModel.Nasdaq = await _majorIndexService.GetMajorIndex(MajorIndexType.Nasdaq); 40 | } 41 | 42 | private async Task LoadSP500() 43 | { 44 | _majorIndexListingViewModel.SP500 = await _majorIndexService.GetMajorIndex(MajorIndexType.SP500); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/LoginCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.WPF.State.Authenticators; 3 | using SimpleTrader.WPF.State.Navigators; 4 | using SimpleTrader.WPF.ViewModels; 5 | using SimpleTrader.WPF.ViewModels.Factories; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Input; 12 | 13 | namespace SimpleTrader.WPF.Commands 14 | { 15 | public class LoginCommand : AsyncCommandBase 16 | { 17 | private readonly LoginViewModel _loginViewModel; 18 | private readonly IAuthenticator _authenticator; 19 | private readonly IRenavigator _renavigator; 20 | 21 | public LoginCommand(LoginViewModel loginViewModel, IAuthenticator authenticator, IRenavigator renavigator) 22 | { 23 | _loginViewModel = loginViewModel; 24 | _authenticator = authenticator; 25 | _renavigator = renavigator; 26 | 27 | _loginViewModel.PropertyChanged += LoginViewModel_PropertyChanged; 28 | } 29 | 30 | public override bool CanExecute(object parameter) 31 | { 32 | return _loginViewModel.CanLogin && base.CanExecute(parameter); 33 | } 34 | 35 | public override async Task ExecuteAsync(object parameter) 36 | { 37 | _loginViewModel.ErrorMessage = string.Empty; 38 | 39 | try 40 | { 41 | await _authenticator.Login(_loginViewModel.Username, _loginViewModel.Password); 42 | 43 | _renavigator.Renavigate(); 44 | } 45 | catch (UserNotFoundException) 46 | { 47 | _loginViewModel.ErrorMessage = "Username does not exist."; 48 | } 49 | catch(InvalidPasswordException) 50 | { 51 | _loginViewModel.ErrorMessage = "Incorrect password."; 52 | } 53 | catch(Exception) 54 | { 55 | _loginViewModel.ErrorMessage = "Login failed."; 56 | } 57 | } 58 | 59 | private void LoginViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 60 | { 61 | if(e.PropertyName == nameof(LoginViewModel.CanLogin)) 62 | { 63 | OnCanExecuteChanged(); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/RegisterCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Services.AuthenticationServices; 2 | using SimpleTrader.WPF.State.Authenticators; 3 | using SimpleTrader.WPF.State.Navigators; 4 | using SimpleTrader.WPF.ViewModels; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleTrader.WPF.Commands 12 | { 13 | public class RegisterCommand : AsyncCommandBase 14 | { 15 | private readonly RegisterViewModel _registerViewModel; 16 | private readonly IAuthenticator _authenticator; 17 | private readonly IRenavigator _registerRenavigator; 18 | 19 | public RegisterCommand(RegisterViewModel registerViewModel, IAuthenticator authenticator, IRenavigator registerRenavigator) 20 | { 21 | _registerViewModel = registerViewModel; 22 | _authenticator = authenticator; 23 | _registerRenavigator = registerRenavigator; 24 | 25 | _registerViewModel.PropertyChanged += RegisterViewModel_PropertyChanged; 26 | } 27 | 28 | public override bool CanExecute(object parameter) 29 | { 30 | return _registerViewModel.CanRegister && base.CanExecute(parameter); 31 | } 32 | 33 | public override async Task ExecuteAsync(object parameter) 34 | { 35 | _registerViewModel.ErrorMessage = string.Empty; 36 | 37 | try 38 | { 39 | RegistrationResult registrationResult = await _authenticator.Register( 40 | _registerViewModel.Email, 41 | _registerViewModel.Username, 42 | _registerViewModel.Password, 43 | _registerViewModel.ConfirmPassword); 44 | 45 | switch (registrationResult) 46 | { 47 | case RegistrationResult.Success: 48 | _registerRenavigator.Renavigate(); 49 | break; 50 | case RegistrationResult.PasswordsDoNotMatch: 51 | _registerViewModel.ErrorMessage = "Password does not match confirm password."; 52 | break; 53 | case RegistrationResult.EmailAlreadyExists: 54 | _registerViewModel.ErrorMessage = "An account for this email already exists."; 55 | break; 56 | case RegistrationResult.UsernameAlreadyExists: 57 | _registerViewModel.ErrorMessage = "An account for this username already exists."; 58 | break; 59 | default: 60 | _registerViewModel.ErrorMessage = "Registration failed."; 61 | break; 62 | } 63 | } 64 | catch (Exception) 65 | { 66 | _registerViewModel.ErrorMessage = "Registration failed."; 67 | } 68 | } 69 | 70 | private void RegisterViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 71 | { 72 | if (e.PropertyName == nameof(RegisterViewModel.CanRegister)) 73 | { 74 | OnCanExecuteChanged(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/RenavigateCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.State.Navigators; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Windows.Input; 6 | 7 | namespace SimpleTrader.WPF.Commands 8 | { 9 | public class RenavigateCommand : ICommand 10 | { 11 | private readonly IRenavigator _renavigator; 12 | 13 | public RenavigateCommand(IRenavigator renavigator) 14 | { 15 | _renavigator = renavigator; 16 | } 17 | 18 | public event EventHandler CanExecuteChanged; 19 | 20 | public bool CanExecute(object parameter) 21 | { 22 | return true; 23 | } 24 | 25 | public void Execute(object parameter) 26 | { 27 | _renavigator.Renavigate(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/SearchSymbolCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Services; 3 | using SimpleTrader.WPF.ViewModels; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | 12 | namespace SimpleTrader.WPF.Commands 13 | { 14 | public class SearchSymbolCommand : AsyncCommandBase 15 | { 16 | private readonly ISearchSymbolViewModel _viewModel; 17 | private readonly IStockPriceService _stockPriceService; 18 | 19 | public SearchSymbolCommand(ISearchSymbolViewModel viewModel, IStockPriceService stockPriceService) 20 | { 21 | _viewModel = viewModel; 22 | _stockPriceService = stockPriceService; 23 | 24 | _viewModel.PropertyChanged += ViewModel_PropertyChanged; 25 | } 26 | 27 | public override bool CanExecute(object parameter) 28 | { 29 | return _viewModel.CanSearchSymbol && base.CanExecute(parameter); 30 | } 31 | 32 | public override async Task ExecuteAsync(object parameter) 33 | { 34 | try 35 | { 36 | double stockPrice = await _stockPriceService.GetPrice(_viewModel.Symbol); 37 | 38 | _viewModel.SearchResultSymbol = _viewModel.Symbol.ToUpper(); 39 | _viewModel.StockPrice = stockPrice; 40 | } 41 | catch (InvalidSymbolException) 42 | { 43 | _viewModel.ErrorMessage = "Symbol does not exist."; 44 | } 45 | catch (Exception) 46 | { 47 | _viewModel.ErrorMessage = "Failed to get symbol information."; 48 | } 49 | } 50 | 51 | private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 52 | { 53 | if (e.PropertyName == nameof(ISearchSymbolViewModel.CanSearchSymbol)) 54 | { 55 | OnCanExecuteChanged(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/SellStockCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Exceptions; 2 | using SimpleTrader.Domain.Models; 3 | using SimpleTrader.Domain.Services.TransactionServices; 4 | using SimpleTrader.WPF.State.Accounts; 5 | using SimpleTrader.WPF.ViewModels; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace SimpleTrader.WPF.Commands 13 | { 14 | public class SellStockCommand : AsyncCommandBase 15 | { 16 | private readonly SellViewModel _viewModel; 17 | private readonly ISellStockService _sellStockService; 18 | private readonly IAccountStore _accountStore; 19 | 20 | public SellStockCommand(SellViewModel viewModel, ISellStockService sellStockService, IAccountStore accountStore) 21 | { 22 | _viewModel = viewModel; 23 | _sellStockService = sellStockService; 24 | _accountStore = accountStore; 25 | 26 | _viewModel.PropertyChanged += ViewModel_PropertyChanged; 27 | } 28 | 29 | public override bool CanExecute(object parameter) 30 | { 31 | return _viewModel.CanSellStock && base.CanExecute(parameter); 32 | } 33 | 34 | public override async Task ExecuteAsync(object parameter) 35 | { 36 | _viewModel.StatusMessage = string.Empty; 37 | _viewModel.ErrorMessage = string.Empty; 38 | 39 | try 40 | { 41 | string symbol = _viewModel.Symbol; 42 | int shares = _viewModel.SharesToSell; 43 | Account account = await _sellStockService.SellStock(_accountStore.CurrentAccount, symbol, shares); 44 | 45 | _accountStore.CurrentAccount = account; 46 | 47 | _viewModel.SearchResultSymbol = string.Empty; 48 | _viewModel.StatusMessage = $"Successfully sold {shares} shares of {symbol}."; 49 | } 50 | catch (InsufficientSharesException ex) 51 | { 52 | _viewModel.ErrorMessage = $"Account has insufficient shares. You only have {ex.AccountShares} shares."; 53 | } 54 | catch (InvalidSymbolException) 55 | { 56 | _viewModel.ErrorMessage = "Symbol does not exist."; 57 | } 58 | catch (Exception) 59 | { 60 | _viewModel.ErrorMessage = "Transaction failed."; 61 | } 62 | } 63 | 64 | private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 65 | { 66 | if (e.PropertyName == nameof(SellViewModel.CanSellStock)) 67 | { 68 | OnCanExecuteChanged(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Commands/UpdateCurrentViewModelCommand.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.FinancialModelingPrepAPI.Services; 2 | using SimpleTrader.WPF.State.Navigators; 3 | using SimpleTrader.WPF.ViewModels; 4 | using SimpleTrader.WPF.ViewModels.Factories; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Windows.Input; 9 | 10 | namespace SimpleTrader.WPF.Commands 11 | { 12 | public class UpdateCurrentViewModelCommand : ICommand 13 | { 14 | public event EventHandler CanExecuteChanged; 15 | 16 | private readonly INavigator _navigator; 17 | private readonly ISimpleTraderViewModelFactory _viewModelFactory; 18 | 19 | public UpdateCurrentViewModelCommand(INavigator navigator, ISimpleTraderViewModelFactory viewModelFactory) 20 | { 21 | _navigator = navigator; 22 | _viewModelFactory = viewModelFactory; 23 | } 24 | 25 | public bool CanExecute(object parameter) 26 | { 27 | return true; 28 | } 29 | 30 | public void Execute(object parameter) 31 | { 32 | if(parameter is ViewType) 33 | { 34 | ViewType viewType = (ViewType)parameter; 35 | 36 | _navigator.CurrentViewModel = _viewModelFactory.CreateViewModel(viewType); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/AssetListing.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/AssetListing.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Data; 8 | using System.Windows.Documents; 9 | using System.Windows.Input; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Imaging; 12 | using System.Windows.Navigation; 13 | using System.Windows.Shapes; 14 | 15 | namespace SimpleTrader.WPF.Controls 16 | { 17 | /// 18 | /// Interaction logic for AssetListing.xaml 19 | /// 20 | public partial class AssetListing : UserControl 21 | { 22 | public AssetListing() 23 | { 24 | InitializeComponent(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/AssetSummary.xaml: -------------------------------------------------------------------------------- 1 |  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 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/AssetSummary.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Imaging; 11 | using System.Windows.Navigation; 12 | using System.Windows.Shapes; 13 | 14 | namespace SimpleTrader.WPF.Controls 15 | { 16 | /// 17 | /// Interaction logic for AssetSummary.xaml 18 | /// 19 | public partial class AssetSummary : UserControl 20 | { 21 | public AssetSummary() 22 | { 23 | InitializeComponent(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/MajorIndexCard.xaml: -------------------------------------------------------------------------------- 1 |  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 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/MajorIndexCard.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Imaging; 11 | using System.Windows.Navigation; 12 | using System.Windows.Shapes; 13 | 14 | namespace SimpleTrader.WPF.Controls 15 | { 16 | /// 17 | /// Interaction logic for MajorIndexCard.xaml 18 | /// 19 | public partial class MajorIndexCard : UserControl 20 | { 21 | public MajorIndexCard() 22 | { 23 | InitializeComponent(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/MajorIndexListing.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 48 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 77 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/MajorIndexListing.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Imaging; 11 | using System.Windows.Navigation; 12 | using System.Windows.Shapes; 13 | 14 | namespace SimpleTrader.WPF.Controls 15 | { 16 | /// 17 | /// Interaction logic for MajorIndexListing.xaml 18 | /// 19 | public partial class MajorIndexListing : UserControl 20 | { 21 | public MajorIndexListing() 22 | { 23 | InitializeComponent(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/NavigationBar.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 34 | 35 | 36 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 36 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Controls/SearchSymbolResultPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Imaging; 11 | using System.Windows.Navigation; 12 | using System.Windows.Shapes; 13 | 14 | namespace SimpleTrader.WPF.Controls 15 | { 16 | /// 17 | /// Interaction logic for SearchSymbolResultPanel.xaml 18 | /// 19 | public partial class SearchSymbolResultPanel : UserControl 20 | { 21 | public SearchSymbolResultPanel() 22 | { 23 | InitializeComponent(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Converters/EqualValueToParameterConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Windows.Data; 6 | 7 | namespace SimpleTrader.WPF.Converters 8 | { 9 | public class EqualValueToParameterConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return value.ToString() == parameter.ToString(); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddConfigurationHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Hosting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace SimpleTrader.WPF.HostBuilders 8 | { 9 | public static class AddConfigurationHostBuilderExtensions 10 | { 11 | public static IHostBuilder AddConfiguration(this IHostBuilder host) 12 | { 13 | host.ConfigureAppConfiguration(c => 14 | { 15 | c.AddJsonFile("appsettings.json"); 16 | c.AddEnvironmentVariables(); 17 | }); 18 | 19 | return host; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddDbContextHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using SimpleTrader.EntityFramework; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace SimpleTrader.WPF.HostBuilders 11 | { 12 | public static class AddDbContextHostBuilderExtensions 13 | { 14 | public static IHostBuilder AddDbContext(this IHostBuilder host) 15 | { 16 | host.ConfigureServices((context, services) => 17 | { 18 | string connectionString = context.Configuration.GetConnectionString("sqlite"); 19 | Action configureDbContext = o => o.UseSqlite(connectionString); 20 | 21 | services.AddDbContext(configureDbContext); 22 | services.AddSingleton(new SimpleTraderDbContextFactory(configureDbContext)); 23 | }); 24 | 25 | return host; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddFinanceAPIHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using SimpleTrader.FinancialModelingPrepAPI; 5 | using SimpleTrader.FinancialModelingPrepAPI.Models; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace SimpleTrader.WPF.HostBuilders 11 | { 12 | public static class AddFinanceAPIHostBuilderExtensions 13 | { 14 | public static IHostBuilder AddFinanceAPI(this IHostBuilder host) 15 | { 16 | host.ConfigureServices((context, services) => 17 | { 18 | string apiKey = context.Configuration.GetValue("FINANCE_API_KEY"); 19 | services.AddSingleton(new FinancialModelingPrepAPIKey(apiKey)); 20 | 21 | services.AddHttpClient(c => 22 | { 23 | c.BaseAddress = new Uri("https://financialmodelingprep.com/api/v3/"); 24 | }); 25 | }); 26 | 27 | return host; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddServicesHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.Identity; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using SimpleTrader.Domain.Models; 5 | using SimpleTrader.Domain.Services; 6 | using SimpleTrader.Domain.Services.AuthenticationServices; 7 | using SimpleTrader.Domain.Services.TransactionServices; 8 | using SimpleTrader.EntityFramework.Services; 9 | using SimpleTrader.FinancialModelingPrepAPI.Services; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Text; 13 | 14 | namespace SimpleTrader.WPF.HostBuilders 15 | { 16 | public static class AddServicesHostBuilderExtensions 17 | { 18 | public static IHostBuilder AddServices(this IHostBuilder host) 19 | { 20 | host.ConfigureServices(services => 21 | { 22 | services.AddSingleton(); 23 | 24 | services.AddSingleton(); 25 | services.AddSingleton, AccountDataService>(); 26 | services.AddSingleton(); 27 | services.AddSingleton(); 28 | services.AddSingleton(); 29 | services.AddSingleton(); 30 | services.AddSingleton(); 31 | }); 32 | 33 | return host; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddStoresHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using SimpleTrader.WPF.State.Accounts; 4 | using SimpleTrader.WPF.State.Assets; 5 | using SimpleTrader.WPF.State.Authenticators; 6 | using SimpleTrader.WPF.State.Navigators; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | 11 | namespace SimpleTrader.WPF.HostBuilders 12 | { 13 | public static class AddStoresHostBuilderExtensions 14 | { 15 | public static IHostBuilder AddStores(this IHostBuilder host) 16 | { 17 | host.ConfigureServices(services => 18 | { 19 | services.AddSingleton(); 20 | services.AddSingleton(); 21 | services.AddSingleton(); 22 | services.AddSingleton(); 23 | }); 24 | 25 | return host; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddViewModelsHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using SimpleTrader.Domain.Services; 4 | using SimpleTrader.WPF.State.Assets; 5 | using SimpleTrader.WPF.State.Authenticators; 6 | using SimpleTrader.WPF.State.Navigators; 7 | using SimpleTrader.WPF.ViewModels; 8 | using SimpleTrader.WPF.ViewModels.Factories; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | 13 | namespace SimpleTrader.WPF.HostBuilders 14 | { 15 | public static class AddViewModelsHostBuilderExtensions 16 | { 17 | public static IHostBuilder AddViewModels(this IHostBuilder host) 18 | { 19 | host.ConfigureServices(services => 20 | { 21 | services.AddTransient(CreateHomeViewModel); 22 | services.AddTransient(); 23 | services.AddTransient(); 24 | services.AddTransient(); 25 | services.AddTransient(); 26 | services.AddTransient(); 27 | 28 | services.AddSingleton>(services => () => services.GetRequiredService()); 29 | services.AddSingleton>(services => () => services.GetRequiredService()); 30 | services.AddSingleton>(services => () => services.GetRequiredService()); 31 | services.AddSingleton>(services => () => services.GetRequiredService()); 32 | services.AddSingleton>(services => () => CreateLoginViewModel(services)); 33 | services.AddSingleton>(services => () => CreateRegisterViewModel(services)); 34 | 35 | services.AddSingleton(); 36 | 37 | services.AddSingleton>(); 38 | services.AddSingleton>(); 39 | services.AddSingleton>(); 40 | }); 41 | 42 | return host; 43 | } 44 | 45 | private static HomeViewModel CreateHomeViewModel(IServiceProvider services) 46 | { 47 | return new HomeViewModel( 48 | services.GetRequiredService(), 49 | MajorIndexListingViewModel.LoadMajorIndexViewModel(services.GetRequiredService())); 50 | } 51 | 52 | private static LoginViewModel CreateLoginViewModel(IServiceProvider services) 53 | { 54 | return new LoginViewModel( 55 | services.GetRequiredService(), 56 | services.GetRequiredService>(), 57 | services.GetRequiredService>()); 58 | } 59 | 60 | private static RegisterViewModel CreateRegisterViewModel(IServiceProvider services) 61 | { 62 | return new RegisterViewModel( 63 | services.GetRequiredService(), 64 | services.GetRequiredService>(), 65 | services.GetRequiredService>()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/HostBuilders/AddViewsHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using SimpleTrader.WPF.ViewModels; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace SimpleTrader.WPF.HostBuilders 9 | { 10 | public static class AddViewsHostBuilderExtensions 11 | { 12 | public static IHostBuilder AddViews(this IHostBuilder host) 13 | { 14 | host.ConfigureServices(services => 15 | { 16 | services.AddSingleton(s => new MainWindow(s.GetRequiredService())); 17 | }); 18 | 19 | return host; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SimpleTrader.WPF 17 | { 18 | /// 19 | /// Interaction logic for MainWindow.xaml 20 | /// 21 | public partial class MainWindow : Window 22 | { 23 | public MainWindow(object dataContext) 24 | { 25 | InitializeComponent(); 26 | 27 | DataContext = dataContext; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Resources/login-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingletonSean/SimpleTrader/564e87a4299498062de33df97b43a0347aead463/SimpleTrader/SimpleTrader.WPF/Resources/login-background.jpg -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/SimpleTrader.WPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net5.0-windows 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/SimpleTrader.WPF.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Designer 7 | 8 | 9 | 10 | 11 | Code 12 | 13 | 14 | Code 15 | 16 | 17 | Code 18 | 19 | 20 | Code 21 | 22 | 23 | Code 24 | 25 | 26 | Code 27 | 28 | 29 | Code 30 | 31 | 32 | Code 33 | 34 | 35 | Code 36 | 37 | 38 | Code 39 | 40 | 41 | Code 42 | 43 | 44 | Code 45 | 46 | 47 | 48 | 49 | Designer 50 | 51 | 52 | Designer 53 | 54 | 55 | Designer 56 | 57 | 58 | Designer 59 | 60 | 61 | Designer 62 | 63 | 64 | Designer 65 | 66 | 67 | Designer 68 | 69 | 70 | Designer 71 | 72 | 73 | Designer 74 | 75 | 76 | Designer 77 | 78 | 79 | Designer 80 | 81 | 82 | Designer 83 | 84 | 85 | Designer 86 | 87 | 88 | Designer 89 | 90 | 91 | Designer 92 | 93 | 94 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Accounts/AccountStore.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.State.Accounts 7 | { 8 | public class AccountStore : IAccountStore 9 | { 10 | private Account _currentAccount; 11 | public Account CurrentAccount 12 | { 13 | get 14 | { 15 | return _currentAccount; 16 | } 17 | set 18 | { 19 | _currentAccount = value; 20 | StateChanged?.Invoke(); 21 | } 22 | } 23 | 24 | public event Action StateChanged; 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Accounts/IAccountStore.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.State.Accounts 7 | { 8 | public interface IAccountStore 9 | { 10 | Account CurrentAccount { get; set; } 11 | event Action StateChanged; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Assets/AssetStore.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.WPF.State.Accounts; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace SimpleTrader.WPF.State.Assets 8 | { 9 | public class AssetStore 10 | { 11 | private readonly IAccountStore _accountStore; 12 | 13 | public double AccountBalance => _accountStore.CurrentAccount?.Balance ?? 0; 14 | public IEnumerable AssetTransactions => _accountStore.CurrentAccount?.AssetTransactions ?? new List(); 15 | 16 | public event Action StateChanged; 17 | 18 | public AssetStore(IAccountStore accountStore) 19 | { 20 | _accountStore = accountStore; 21 | 22 | _accountStore.StateChanged += OnStateChanged; 23 | } 24 | 25 | private void OnStateChanged() 26 | { 27 | StateChanged?.Invoke(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Authenticators/Authenticator.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Services.AuthenticationServices; 3 | using SimpleTrader.WPF.State.Accounts; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.WPF.State.Authenticators 10 | { 11 | public class Authenticator : IAuthenticator 12 | { 13 | private readonly IAuthenticationService _authenticationService; 14 | private readonly IAccountStore _accountStore; 15 | 16 | public Authenticator(IAuthenticationService authenticationService, IAccountStore accountStore) 17 | { 18 | _authenticationService = authenticationService; 19 | _accountStore = accountStore; 20 | } 21 | 22 | public Account CurrentAccount 23 | { 24 | get 25 | { 26 | return _accountStore.CurrentAccount; 27 | } 28 | private set 29 | { 30 | _accountStore.CurrentAccount = value; 31 | StateChanged?.Invoke(); 32 | } 33 | } 34 | 35 | public bool IsLoggedIn => CurrentAccount != null; 36 | 37 | public event Action StateChanged; 38 | 39 | public async Task Login(string username, string password) 40 | { 41 | CurrentAccount = await _authenticationService.Login(username, password); 42 | } 43 | 44 | public void Logout() 45 | { 46 | CurrentAccount = null; 47 | } 48 | 49 | public async Task Register(string email, string username, string password, string confirmPassword) 50 | { 51 | return await _authenticationService.Register(email, username, password, confirmPassword); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Authenticators/IAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Exceptions; 3 | using SimpleTrader.Domain.Services.AuthenticationServices; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleTrader.WPF.State.Authenticators 10 | { 11 | public interface IAuthenticator 12 | { 13 | Account CurrentAccount { get; } 14 | bool IsLoggedIn { get; } 15 | 16 | event Action StateChanged; 17 | 18 | /// 19 | /// Register a new user. 20 | /// 21 | /// The user's email. 22 | /// The user's name. 23 | /// The user's password. 24 | /// The user's confirmed password. 25 | /// The result of the registration. 26 | /// Thrown if the registration fails. 27 | Task Register(string email, string username, string password, string confirmPassword); 28 | 29 | /// 30 | /// Login to the application. 31 | /// 32 | /// The user's name. 33 | /// The user's password. 34 | /// Thrown if the user does not exist. 35 | /// Thrown if the password is invalid. 36 | /// Thrown if the login fails. 37 | Task Login(string username, string password); 38 | 39 | void Logout(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Navigators/INavigator.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Windows.Input; 6 | 7 | namespace SimpleTrader.WPF.State.Navigators 8 | { 9 | public enum ViewType 10 | { 11 | Login, 12 | Home, 13 | Portfolio, 14 | Buy, 15 | Sell 16 | } 17 | 18 | public interface INavigator 19 | { 20 | ViewModelBase CurrentViewModel { get; set; } 21 | event Action StateChanged; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Navigators/IRenavigator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.WPF.State.Navigators 6 | { 7 | public interface IRenavigator 8 | { 9 | void Renavigate(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Navigators/Navigator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Text; 5 | using System.Windows.Input; 6 | using SimpleTrader.WPF.Commands; 7 | using SimpleTrader.WPF.ViewModels; 8 | using SimpleTrader.WPF.ViewModels.Factories; 9 | 10 | namespace SimpleTrader.WPF.State.Navigators 11 | { 12 | public class Navigator : INavigator 13 | { 14 | private ViewModelBase _currentViewModel; 15 | public ViewModelBase CurrentViewModel 16 | { 17 | get 18 | { 19 | return _currentViewModel; 20 | } 21 | set 22 | { 23 | _currentViewModel?.Dispose(); 24 | 25 | _currentViewModel = value; 26 | StateChanged?.Invoke(); 27 | } 28 | } 29 | 30 | public event Action StateChanged; 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/State/Navigators/ViewModelDelegateRenavigator.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.ViewModels; 2 | using SimpleTrader.WPF.ViewModels.Factories; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace SimpleTrader.WPF.State.Navigators 8 | { 9 | public class ViewModelDelegateRenavigator : IRenavigator where TViewModel : ViewModelBase 10 | { 11 | private readonly INavigator _navigator; 12 | private readonly CreateViewModel _createViewModel; 13 | 14 | public ViewModelDelegateRenavigator(INavigator navigator, CreateViewModel createViewModel) 15 | { 16 | _navigator = navigator; 17 | _createViewModel = createViewModel; 18 | } 19 | 20 | public void Renavigate() 21 | { 22 | _navigator.CurrentViewModel = _createViewModel(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Styles/Common.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | #799540 6 | #50632b 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 | 55 | 56 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Styles/NavigationBar.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 43 | 44 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/AssetListingViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.State.Assets; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace SimpleTrader.WPF.ViewModels 9 | { 10 | public class AssetListingViewModel : ViewModelBase 11 | { 12 | private readonly AssetStore _assetStore; 13 | private readonly Func, IEnumerable> _filterAssets; 14 | private readonly ObservableCollection _assets; 15 | 16 | public IEnumerable Assets => _assets; 17 | 18 | public AssetListingViewModel(AssetStore assetStore) : this(assetStore, assets => assets) { } 19 | 20 | public AssetListingViewModel(AssetStore assetStore, Func, IEnumerable> filterAssets) 21 | { 22 | _assetStore = assetStore; 23 | _filterAssets = filterAssets; 24 | _assets = new ObservableCollection(); 25 | 26 | _assetStore.StateChanged += AssetStore_StateChanged; 27 | 28 | ResetAssets(); 29 | } 30 | 31 | private void ResetAssets() 32 | { 33 | IEnumerable assetViewModels = _assetStore.AssetTransactions 34 | .GroupBy(t => t.Asset.Symbol) 35 | .Select(g => new AssetViewModel(g.Key, g.Sum(a => a.IsPurchase ? a.Shares : -a.Shares))) 36 | .Where(a => a.Shares > 0) 37 | .OrderByDescending(a => a.Shares); 38 | 39 | assetViewModels = _filterAssets(assetViewModels); 40 | 41 | DisposeAssets(); 42 | _assets.Clear(); 43 | foreach (AssetViewModel viewModel in assetViewModels) 44 | { 45 | _assets.Add(viewModel); 46 | } 47 | } 48 | 49 | private void DisposeAssets() 50 | { 51 | foreach (AssetViewModel asset in _assets) 52 | { 53 | asset.Dispose(); 54 | } 55 | } 56 | 57 | private void AssetStore_StateChanged() 58 | { 59 | ResetAssets(); 60 | } 61 | 62 | public override void Dispose() 63 | { 64 | _assetStore.StateChanged -= AssetStore_StateChanged; 65 | DisposeAssets(); 66 | 67 | base.Dispose(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/AssetSummaryViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.WPF.State.Assets; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace SimpleTrader.WPF.ViewModels 10 | { 11 | public class AssetSummaryViewModel : ViewModelBase 12 | { 13 | private readonly AssetStore _assetStore; 14 | 15 | public double AccountBalance => _assetStore.AccountBalance; 16 | public AssetListingViewModel AssetListingViewModel { get; } 17 | 18 | public AssetSummaryViewModel(AssetStore assetStore) 19 | { 20 | _assetStore = assetStore; 21 | AssetListingViewModel = new AssetListingViewModel(assetStore, assets => assets.Take(3)); 22 | 23 | _assetStore.StateChanged += AssetStore_StateChanged; 24 | } 25 | 26 | private void AssetStore_StateChanged() 27 | { 28 | OnPropertyChanged(nameof(AccountBalance)); 29 | } 30 | 31 | public override void Dispose() 32 | { 33 | _assetStore.StateChanged -= AssetStore_StateChanged; 34 | AssetListingViewModel.Dispose(); 35 | 36 | base.Dispose(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/AssetViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.WPF.ViewModels 6 | { 7 | public class AssetViewModel : ViewModelBase 8 | { 9 | public string Symbol { get; } 10 | public int Shares { get; } 11 | 12 | public AssetViewModel(string symbol, int shares) 13 | { 14 | Symbol = symbol; 15 | Shares = shares; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/BuyViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Services; 2 | using SimpleTrader.Domain.Services.TransactionServices; 3 | using SimpleTrader.WPF.Commands; 4 | using SimpleTrader.WPF.State.Accounts; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Windows.Input; 9 | 10 | namespace SimpleTrader.WPF.ViewModels 11 | { 12 | public class BuyViewModel : ViewModelBase, ISearchSymbolViewModel 13 | { 14 | private string _symbol; 15 | public string Symbol 16 | { 17 | get 18 | { 19 | return _symbol; 20 | } 21 | set 22 | { 23 | _symbol = value; 24 | OnPropertyChanged(nameof(Symbol)); 25 | OnPropertyChanged(nameof(CanSearchSymbol)); 26 | } 27 | } 28 | 29 | public bool CanSearchSymbol => !string.IsNullOrEmpty(Symbol); 30 | 31 | private string _searchResultSymbol = string.Empty; 32 | public string SearchResultSymbol 33 | { 34 | get 35 | { 36 | return _searchResultSymbol; 37 | } 38 | set 39 | { 40 | _searchResultSymbol = value; 41 | OnPropertyChanged(nameof(SearchResultSymbol)); 42 | } 43 | } 44 | 45 | private double _stockPrice; 46 | public double StockPrice 47 | { 48 | get 49 | { 50 | return _stockPrice; 51 | } 52 | set 53 | { 54 | _stockPrice = value; 55 | OnPropertyChanged(nameof(StockPrice)); 56 | OnPropertyChanged(nameof(TotalPrice)); 57 | } 58 | } 59 | 60 | private int _sharesToBuy; 61 | public int SharesToBuy 62 | { 63 | get 64 | { 65 | return _sharesToBuy; 66 | } 67 | set 68 | { 69 | _sharesToBuy = value; 70 | OnPropertyChanged(nameof(SharesToBuy)); 71 | OnPropertyChanged(nameof(TotalPrice)); 72 | OnPropertyChanged(nameof(CanBuyStock)); 73 | } 74 | } 75 | 76 | public bool CanBuyStock => SharesToBuy > 0; 77 | 78 | public double TotalPrice 79 | { 80 | get 81 | { 82 | return SharesToBuy * StockPrice; 83 | } 84 | } 85 | 86 | public MessageViewModel ErrorMessageViewModel { get; } 87 | 88 | public string ErrorMessage 89 | { 90 | set => ErrorMessageViewModel.Message = value; 91 | } 92 | 93 | public MessageViewModel StatusMessageViewModel { get; } 94 | 95 | public string StatusMessage 96 | { 97 | set => StatusMessageViewModel.Message = value; 98 | } 99 | 100 | public ICommand SearchSymbolCommand { get; set; } 101 | public ICommand BuyStockCommand { get; set; } 102 | 103 | public BuyViewModel(IStockPriceService stockPriceService, IBuyStockService buyStockService, IAccountStore accountStore) 104 | { 105 | ErrorMessageViewModel = new MessageViewModel(); 106 | StatusMessageViewModel = new MessageViewModel(); 107 | 108 | SearchSymbolCommand = new SearchSymbolCommand(this, stockPriceService); 109 | BuyStockCommand = new BuyStockCommand(this, buyStockService, accountStore); 110 | } 111 | 112 | public override void Dispose() 113 | { 114 | ErrorMessageViewModel.Dispose(); 115 | StatusMessageViewModel.Dispose(); 116 | 117 | base.Dispose(); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/Factories/ISimpleTraderViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.State.Navigators; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.ViewModels.Factories 7 | { 8 | public interface ISimpleTraderViewModelFactory 9 | { 10 | ViewModelBase CreateViewModel(ViewType viewType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/Factories/SimpleTraderViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.State.Navigators; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.ViewModels.Factories 7 | { 8 | public class SimpleTraderViewModelFactory : ISimpleTraderViewModelFactory 9 | { 10 | private readonly CreateViewModel _createHomeViewModel; 11 | private readonly CreateViewModel _createPortfolioViewModel; 12 | private readonly CreateViewModel _createLoginViewModel; 13 | private readonly CreateViewModel _createBuyViewModel; 14 | private readonly CreateViewModel _createSellViewModel; 15 | 16 | public SimpleTraderViewModelFactory(CreateViewModel createHomeViewModel, 17 | CreateViewModel createPortfolioViewModel, 18 | CreateViewModel createLoginViewModel, 19 | CreateViewModel createBuyViewModel, 20 | CreateViewModel createSellViewModel) 21 | { 22 | _createHomeViewModel = createHomeViewModel; 23 | _createPortfolioViewModel = createPortfolioViewModel; 24 | _createLoginViewModel = createLoginViewModel; 25 | _createBuyViewModel = createBuyViewModel; 26 | _createSellViewModel = createSellViewModel; 27 | } 28 | 29 | public ViewModelBase CreateViewModel(ViewType viewType) 30 | { 31 | switch (viewType) 32 | { 33 | case ViewType.Login: 34 | return _createLoginViewModel(); 35 | case ViewType.Home: 36 | return _createHomeViewModel(); 37 | case ViewType.Portfolio: 38 | return _createPortfolioViewModel(); 39 | case ViewType.Buy: 40 | return _createBuyViewModel(); 41 | case ViewType.Sell: 42 | return _createSellViewModel(); 43 | default: 44 | throw new ArgumentException("The ViewType does not have a ViewModel.", "viewType"); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/HomeViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.FinancialModelingPrepAPI.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.ViewModels 7 | { 8 | public class HomeViewModel : ViewModelBase 9 | { 10 | public AssetSummaryViewModel AssetSummaryViewModel { get; } 11 | public MajorIndexListingViewModel MajorIndexListingViewModel { get; } 12 | 13 | public HomeViewModel(AssetSummaryViewModel assetSummaryViewModel, MajorIndexListingViewModel majorIndexListingViewModel) 14 | { 15 | AssetSummaryViewModel = assetSummaryViewModel; 16 | MajorIndexListingViewModel = majorIndexListingViewModel; 17 | } 18 | 19 | public override void Dispose() 20 | { 21 | AssetSummaryViewModel.Dispose(); 22 | MajorIndexListingViewModel.Dispose(); 23 | 24 | base.Dispose(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/ISearchSymbolViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace SimpleTrader.WPF.ViewModels 4 | { 5 | public interface ISearchSymbolViewModel : INotifyPropertyChanged 6 | { 7 | string ErrorMessage { set; } 8 | string SearchResultSymbol { set; } 9 | double StockPrice { set; } 10 | string Symbol { get; } 11 | bool CanSearchSymbol { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.Commands; 2 | using SimpleTrader.WPF.State.Authenticators; 3 | using SimpleTrader.WPF.State.Navigators; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Windows.Input; 8 | 9 | namespace SimpleTrader.WPF.ViewModels 10 | { 11 | public class LoginViewModel : ViewModelBase 12 | { 13 | private string _username = "SingletonSean"; 14 | public string Username 15 | { 16 | get 17 | { 18 | return _username; 19 | } 20 | set 21 | { 22 | _username = value; 23 | OnPropertyChanged(nameof(Username)); 24 | OnPropertyChanged(nameof(CanLogin)); 25 | } 26 | } 27 | 28 | private string _password; 29 | public string Password 30 | { 31 | get 32 | { 33 | return _password; 34 | } 35 | set 36 | { 37 | _password = value; 38 | OnPropertyChanged(nameof(Password)); 39 | OnPropertyChanged(nameof(CanLogin)); 40 | } 41 | } 42 | 43 | public bool CanLogin => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); 44 | 45 | public MessageViewModel ErrorMessageViewModel { get; } 46 | 47 | public string ErrorMessage 48 | { 49 | set => ErrorMessageViewModel.Message = value; 50 | } 51 | 52 | public ICommand LoginCommand { get; } 53 | public ICommand ViewRegisterCommand { get; } 54 | 55 | public LoginViewModel(IAuthenticator authenticator, IRenavigator loginRenavigator, IRenavigator registerRenavigator) 56 | { 57 | ErrorMessageViewModel = new MessageViewModel(); 58 | 59 | LoginCommand = new LoginCommand(this, authenticator, loginRenavigator); 60 | ViewRegisterCommand = new RenavigateCommand(registerRenavigator); 61 | } 62 | 63 | public override void Dispose() 64 | { 65 | ErrorMessageViewModel.Dispose(); 66 | 67 | base.Dispose(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.Commands; 2 | using SimpleTrader.WPF.State.Authenticators; 3 | using SimpleTrader.WPF.State.Navigators; 4 | using SimpleTrader.WPF.ViewModels.Factories; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Windows.Input; 9 | 10 | namespace SimpleTrader.WPF.ViewModels 11 | { 12 | public class MainViewModel : ViewModelBase 13 | { 14 | private readonly ISimpleTraderViewModelFactory _viewModelFactory; 15 | private readonly INavigator _navigator; 16 | private readonly IAuthenticator _authenticator; 17 | 18 | public bool IsLoggedIn => _authenticator.IsLoggedIn; 19 | public ViewModelBase CurrentViewModel => _navigator.CurrentViewModel; 20 | 21 | public ICommand UpdateCurrentViewModelCommand { get; } 22 | 23 | public MainViewModel(INavigator navigator, ISimpleTraderViewModelFactory viewModelFactory, IAuthenticator authenticator) 24 | { 25 | _navigator = navigator; 26 | _viewModelFactory = viewModelFactory; 27 | _authenticator = authenticator; 28 | 29 | _navigator.StateChanged += Navigator_StateChanged; 30 | _authenticator.StateChanged += Authenticator_StateChanged; 31 | 32 | UpdateCurrentViewModelCommand = new UpdateCurrentViewModelCommand(navigator, _viewModelFactory); 33 | UpdateCurrentViewModelCommand.Execute(ViewType.Login); 34 | } 35 | 36 | private void Authenticator_StateChanged() 37 | { 38 | OnPropertyChanged(nameof(IsLoggedIn)); 39 | } 40 | 41 | private void Navigator_StateChanged() 42 | { 43 | OnPropertyChanged(nameof(CurrentViewModel)); 44 | } 45 | 46 | public override void Dispose() 47 | { 48 | _navigator.StateChanged -= Navigator_StateChanged; 49 | _authenticator.StateChanged -= Authenticator_StateChanged; 50 | 51 | base.Dispose(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/MajorIndexListingViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Models; 2 | using SimpleTrader.Domain.Services; 3 | using SimpleTrader.WPF.Commands; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Input; 9 | 10 | namespace SimpleTrader.WPF.ViewModels 11 | { 12 | public class MajorIndexListingViewModel : ViewModelBase 13 | { 14 | private MajorIndex _dowJones; 15 | public MajorIndex DowJones 16 | { 17 | get 18 | { 19 | return _dowJones; 20 | } 21 | set 22 | { 23 | _dowJones = value; 24 | OnPropertyChanged(nameof(DowJones)); 25 | } 26 | } 27 | 28 | private MajorIndex _nasdaq; 29 | public MajorIndex Nasdaq 30 | { 31 | get 32 | { 33 | return _nasdaq; 34 | } 35 | set 36 | { 37 | _nasdaq = value; 38 | OnPropertyChanged(nameof(Nasdaq)); 39 | } 40 | } 41 | 42 | private MajorIndex _sp500; 43 | public MajorIndex SP500 44 | { 45 | get 46 | { 47 | return _sp500; 48 | } 49 | set 50 | { 51 | _sp500 = value; 52 | OnPropertyChanged(nameof(SP500)); 53 | } 54 | } 55 | 56 | private bool _isLoading; 57 | public bool IsLoading 58 | { 59 | get 60 | { 61 | return _isLoading; 62 | } 63 | set 64 | { 65 | _isLoading = value; 66 | OnPropertyChanged(nameof(IsLoading)); 67 | } 68 | } 69 | 70 | public ICommand LoadMajorIndexesCommand { get; } 71 | 72 | public MajorIndexListingViewModel(IMajorIndexService majorIndexService) 73 | { 74 | LoadMajorIndexesCommand = new LoadMajorIndexesCommand(this, majorIndexService); 75 | } 76 | 77 | public static MajorIndexListingViewModel LoadMajorIndexViewModel(IMajorIndexService majorIndexService) 78 | { 79 | MajorIndexListingViewModel majorIndexViewModel = new MajorIndexListingViewModel(majorIndexService); 80 | 81 | majorIndexViewModel.LoadMajorIndexesCommand.Execute(null); 82 | 83 | return majorIndexViewModel; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/MessageViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleTrader.WPF.ViewModels 6 | { 7 | public class MessageViewModel : ViewModelBase 8 | { 9 | private string _message; 10 | public string Message 11 | { 12 | get 13 | { 14 | return _message; 15 | } 16 | set 17 | { 18 | _message = value; 19 | OnPropertyChanged(nameof(Message)); 20 | OnPropertyChanged(nameof(HasMessage)); 21 | } 22 | } 23 | 24 | public bool HasMessage => !string.IsNullOrEmpty(Message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/PortfolioViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.State.Assets; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace SimpleTrader.WPF.ViewModels 9 | { 10 | public class PortfolioViewModel : ViewModelBase 11 | { 12 | public AssetListingViewModel AssetListingViewModel { get; } 13 | 14 | public PortfolioViewModel(AssetStore assetStore) 15 | { 16 | AssetListingViewModel = new AssetListingViewModel(assetStore); 17 | } 18 | 19 | public override void Dispose() 20 | { 21 | AssetListingViewModel.Dispose(); 22 | 23 | base.Dispose(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.WPF.Commands; 2 | using SimpleTrader.WPF.State.Authenticators; 3 | using SimpleTrader.WPF.State.Navigators; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Windows.Input; 8 | 9 | namespace SimpleTrader.WPF.ViewModels 10 | { 11 | public class RegisterViewModel : ViewModelBase 12 | { 13 | private string _email; 14 | public string Email 15 | { 16 | get 17 | { 18 | return _email; 19 | } 20 | set 21 | { 22 | _email = value; 23 | OnPropertyChanged(nameof(Email)); 24 | OnPropertyChanged(nameof(CanRegister)); 25 | } 26 | } 27 | 28 | private string _username; 29 | public string Username 30 | { 31 | get 32 | { 33 | return _username; 34 | } 35 | set 36 | { 37 | _username = value; 38 | OnPropertyChanged(nameof(Username)); 39 | OnPropertyChanged(nameof(CanRegister)); 40 | } 41 | } 42 | 43 | private string _password; 44 | public string Password 45 | { 46 | get 47 | { 48 | return _password; 49 | } 50 | set 51 | { 52 | _password = value; 53 | OnPropertyChanged(nameof(Password)); 54 | OnPropertyChanged(nameof(CanRegister)); 55 | } 56 | } 57 | 58 | private string _confirmPassword; 59 | public string ConfirmPassword 60 | { 61 | get 62 | { 63 | return _confirmPassword; 64 | } 65 | set 66 | { 67 | _confirmPassword = value; 68 | OnPropertyChanged(nameof(ConfirmPassword)); 69 | OnPropertyChanged(nameof(CanRegister)); 70 | } 71 | } 72 | 73 | public bool CanRegister => !string.IsNullOrEmpty(Email) && 74 | !string.IsNullOrEmpty(Username) && 75 | !string.IsNullOrEmpty(Password) && 76 | !string.IsNullOrEmpty(ConfirmPassword); 77 | 78 | public ICommand RegisterCommand { get; } 79 | 80 | public ICommand ViewLoginCommand { get; } 81 | 82 | public MessageViewModel ErrorMessageViewModel { get; } 83 | 84 | public string ErrorMessage 85 | { 86 | set => ErrorMessageViewModel.Message = value; 87 | } 88 | 89 | public RegisterViewModel(IAuthenticator authenticator, IRenavigator registerRenavigator, IRenavigator loginRenavigator) 90 | { 91 | ErrorMessageViewModel = new MessageViewModel(); 92 | 93 | RegisterCommand = new RegisterCommand(this, authenticator, registerRenavigator); 94 | ViewLoginCommand = new RenavigateCommand(loginRenavigator); 95 | } 96 | 97 | public override void Dispose() 98 | { 99 | ErrorMessageViewModel.Dispose(); 100 | 101 | base.Dispose(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/SellViewModel.cs: -------------------------------------------------------------------------------- 1 | using SimpleTrader.Domain.Services; 2 | using SimpleTrader.Domain.Services.TransactionServices; 3 | using SimpleTrader.WPF.Commands; 4 | using SimpleTrader.WPF.State.Accounts; 5 | using SimpleTrader.WPF.State.Assets; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Windows.Input; 10 | 11 | namespace SimpleTrader.WPF.ViewModels 12 | { 13 | public class SellViewModel : ViewModelBase, ISearchSymbolViewModel 14 | { 15 | public AssetListingViewModel AssetListingViewModel { get; } 16 | 17 | private AssetViewModel _selectedAsset; 18 | public AssetViewModel SelectedAsset 19 | { 20 | get 21 | { 22 | return _selectedAsset; 23 | } 24 | set 25 | { 26 | _selectedAsset = value; 27 | OnPropertyChanged(nameof(SelectedAsset)); 28 | OnPropertyChanged(nameof(Symbol)); 29 | OnPropertyChanged(nameof(CanSearchSymbol)); 30 | } 31 | } 32 | 33 | public string Symbol => SelectedAsset?.Symbol; 34 | 35 | public bool CanSearchSymbol => !string.IsNullOrEmpty(Symbol); 36 | 37 | private string _searchResultSymbol = string.Empty; 38 | public string SearchResultSymbol 39 | { 40 | get 41 | { 42 | return _searchResultSymbol; 43 | } 44 | set 45 | { 46 | _searchResultSymbol = value; 47 | OnPropertyChanged(nameof(SearchResultSymbol)); 48 | } 49 | } 50 | 51 | private double _stockPrice; 52 | public double StockPrice 53 | { 54 | get 55 | { 56 | return _stockPrice; 57 | } 58 | set 59 | { 60 | _stockPrice = value; 61 | OnPropertyChanged(nameof(StockPrice)); 62 | OnPropertyChanged(nameof(TotalPrice)); 63 | } 64 | } 65 | 66 | private int _sharesToSell; 67 | public int SharesToSell 68 | { 69 | get 70 | { 71 | return _sharesToSell; 72 | } 73 | set 74 | { 75 | _sharesToSell = value; 76 | OnPropertyChanged(nameof(SharesToSell)); 77 | OnPropertyChanged(nameof(TotalPrice)); 78 | OnPropertyChanged(nameof(CanSellStock)); 79 | } 80 | } 81 | 82 | public bool CanSellStock => SharesToSell > 0; 83 | 84 | public double TotalPrice => SharesToSell * StockPrice; 85 | 86 | public MessageViewModel ErrorMessageViewModel { get; } 87 | 88 | public string ErrorMessage 89 | { 90 | set => ErrorMessageViewModel.Message = value; 91 | } 92 | 93 | public MessageViewModel StatusMessageViewModel { get; } 94 | 95 | public string StatusMessage 96 | { 97 | set => StatusMessageViewModel.Message = value; 98 | } 99 | 100 | public ICommand SearchSymbolCommand { get; } 101 | public ICommand SellStockCommand { get; } 102 | 103 | public SellViewModel(AssetStore assetStore, 104 | IStockPriceService stockPriceService, 105 | IAccountStore accountStore, 106 | ISellStockService sellStockService) 107 | { 108 | AssetListingViewModel = new AssetListingViewModel(assetStore); 109 | 110 | SearchSymbolCommand = new SearchSymbolCommand(this, stockPriceService); 111 | SellStockCommand = new SellStockCommand(this, sellStockService, accountStore); 112 | 113 | ErrorMessageViewModel = new MessageViewModel(); 114 | StatusMessageViewModel = new MessageViewModel(); 115 | } 116 | 117 | public override void Dispose() 118 | { 119 | AssetListingViewModel.Dispose(); 120 | ErrorMessageViewModel.Dispose(); 121 | StatusMessageViewModel.Dispose(); 122 | 123 | base.Dispose(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Text; 5 | 6 | namespace SimpleTrader.WPF.ViewModels 7 | { 8 | public delegate TViewModel CreateViewModel() where TViewModel : ViewModelBase; 9 | 10 | public class ViewModelBase : INotifyPropertyChanged 11 | { 12 | public virtual void Dispose() { } 13 | 14 | public event PropertyChangedEventHandler PropertyChanged; 15 | 16 | protected void OnPropertyChanged(string propertyName) 17 | { 18 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SimpleTrader/SimpleTrader.WPF/Views/BuyView.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |