Intro
This post is the third post within my series of posts regarding the construction of a trading application using Xamarin.Forms and F#. In this post, I share my source code for selling shares of a stock.
User Interface
The following reflects the user-experience for selling a stock:
The following XAML reflects the above UI:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:manageTrades="clr-namespace:ManageTrades.ViewModels;assembly=ManageTrades" x:Class="FnTrade.SellPage"> <Grid Margin="10,5"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Text="{Binding Symbol, StringFormat='Selling ({0:F0})'}}" FontSize="Large" /> <Label Grid.Row="2" Grid.Column="0" Text="{Binding Shares, StringFormat='{0:F0} shares available'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" /> <Label Grid.Row="3" Grid.Column="0" Text="{Binding StockPrice, StringFormat='{}{0:c} per share'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" /> <Label Grid.Row="4" Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:c} Invested'}}" FontSize="Large" HorizontalOptions="Start" /> <Entry Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding SellQty, Mode=TwoWay}" Placeholder="Enter number of shares to sell" HorizontalTextAlignment="Center" Keyboard="Numeric" /> <Label Grid.Row="7" Grid.Column="1" Text="{Binding SellValue, StringFormat='{}{0:c}'}" HorizontalTextAlignment="End" /> <Button Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Text="Confirm" Command="{Binding Confirm}" IsEnabled="{Binding CanSell}" /> </Grid> </ContentPage>
Here’s some more UI in regards to the user story of selling shares:
ViewModels
The HomeViewmodel was implemented as follows:
namespace Home.ViewModels open System.Windows.Input open Core.IntegrationLogic open Core.Entities open Integration.Factories open Services open Search open System.Collections.ObjectModel type HomeViewModel() as this = inherit ViewModelBase() let dispatcher = getDispatcher() let accountId = getAccountId() let broker = getBroker() :> IBroker let searchCommand = DelegateCommand( (fun _ -> this.StockInfo <- getQuote broker this.Symbol) , fun _ -> true) :> ICommand let sellCommand = DelegateCommand( (fun o -> dispatcher.Sell (o :?> SharesInfo) ) , fun _ -> true) :> ICommand let buyCommand = DelegateCommand( (fun o -> dispatcher.Buy { Owner.AccountId=accountId ; Symbol=o :?> string} ) , fun _ -> true) :> ICommand let mutable symbol = "" let mutable stockInfo = None let mutable investments = ObservableCollection<SharesInfo>() member this.Load() = let result = broker.InvestmentsOf accountId this.Investments <- ObservableCollection<SharesInfo>(result) member this.Symbol with get() = symbol and set(value) = symbol <- value base.NotifyPropertyChanged(<@ this.Symbol @>) member this.StockInfo with get() = stockInfo and set(value) = stockInfo <- value base.NotifyPropertyChanged(<@ this.StockInfo @>) member this.Investments with get() = investments and set(value) = investments <- value base.NotifyPropertyChanged(<@ this.Investments @>) member this.Search = searchCommand member this.Sell = sellCommand member this.Buy = buyCommand
The SellViewModel was implemented as follows:
namespace ManageTrades.ViewModels open System open System.Windows.Input open Core.IntegrationLogic open Core.Entities open Integration.Factories open Services type SellViewModel(info:SharesInfo) as this = inherit ViewModelBase() let dispatcher = getDispatcher() let accountId = getAccountId() let broker = getBroker() :> IBroker let mutable sellQty = "" let mutable canSell = false let mutable sellValue = 0m let confirm = DelegateCommand( (fun _ -> this.ConfirmSell()) , fun _ -> true ) :> ICommand member this.Symbol with get() = info.Shares.Symbol member this.Shares with get() = info.Shares.Qty member this.StockPrice with get() = info.PricePerShare member this.Total with get() = ((decimal)info.Shares.Qty * info.PricePerShare) member this.SellQty with get() = sellQty and set(value) = sellQty <- value let success , validQty = Int32.TryParse sellQty if success then this.CanSell <- validQty > 0 && validQty <= this.Shares else this.CanSell <- false this.UpdateSellValue() base.NotifyPropertyChanged(<@ this.SellQty @>) member this.SellValue with get() = sellValue and set(value) = sellValue <- value base.NotifyPropertyChanged(<@ this.SellValue @>) member this.CanSell with get() = canSell and set(value) = canSell <- value base.NotifyPropertyChanged(<@ this.CanSell @>) member this.Confirm = confirm member private this.ConfirmSell() = if this.CanSell then dispatcher.ConfirmSell { AccountId = accountId Symbol = this.Symbol Quantity = Int32.Parse this.SellQty } member private this.UpdateSellValue() = let success , validQty = Int32.TryParse sellQty if success then this.SellValue <- (decimal)validQty * info.PricePerShare else this.SellValue <- 0m
The RequestSellConfirmedViewModel is as follows:
namespace ManageTrades.ViewModels open Core.IntegrationLogic open Core.Entities open Services open Integration.Factories type RequestSellConfirmedViewModel(request:RequestInfo) = inherit ViewModelBase() let broker = getBroker() :> IBroker let mutable total = 0m member this.Symbol with get() = request.Symbol member this.Quantity with get() = request.Quantity member this.Total with get() = total and set(value) = total <- value base.NotifyPropertyChanged(<@ this.Total @>) member this.Load() = match broker.GetInfo request.Symbol with | Some info -> this.Total <- info.Price * (decimal) request.Quantity | None -> failwith (sprintf "Failed to retrieve stock information for %s" this.Symbol)
Application Logic
My app subscribes to events so that it can manage page navigation:
public partial class App : Application { public App() { InitializeComponent(); MainPage = new NavigationPage(new HomePage()); } protected override void OnStart() { getDispatcher().SellRequested += async (s, e) => await MainPage.Navigation.PushAsync( new SellPage(new SellViewModel(e as SharesInfo))); getDispatcher().ExecuteSellRequested += async (s, e) => await MainPage.Navigation.PushAsync( new RequestSellConfirmedPage(new RequestSellConfirmedViewModel(e as RequestInfo))); } protected override void OnSleep() { } protected override void OnResume() { } } }
The following are events that are declared within my Dispatcher class:
namespace Services open System.Diagnostics open System type Dispatcher() = let sellRequested = new Event<EventHandler<_>,_>() let buyRequested = new Event<EventHandler<_>,_>() let confirmSellRequested = new Event<EventHandler<_>,_>() let executeSellRequested = new Event<EventHandler<_>,_>() [<CLIEvent>] member this.SellRequested = sellRequested.Publish member this.Sell owner = sellRequested.Trigger(this , owner) [<CLIEvent>] member this.BuyRequested = buyRequested.Publish member this.Buy owner = buyRequested.Trigger(this , owner) [<CLIEvent>] member this.ConfirmSellRequested = confirmSellRequested.Publish member this.ConfirmSell info = confirmSellRequested.Trigger(this , info) [<CLIEvent>] member this.ExecuteSellRequested = executeSellRequested.Publish member this.ExecuteSell info = executeSellRequested.Trigger(this , info)
Domain Types
The following are my domain types:
module Core.Entities type StockInfo = { Symbol : string Price : decimal DayLow : decimal DayHigh : decimal } type Shares = { AccountId : string Symbol : string Qty : int } type SharesInfo ={ Shares : Shares PricePerShare : decimal Total : decimal } type Owner = { AccountId:string ; Symbol:string } type RequestInfo = { AccountId : string Symbol : string Quantity : int } type InsufficientFunds = { PurchaseAttempt : RequestInfo Balance : decimal StockPrice : decimal } type PurchaseResult = | PurchaseRequested of RequestInfo | UnknownSymbol of RequestInfo | InvalidQuantity of RequestInfo | InsufficientFunds of InsufficientFunds type SellResult = | SellRequested of RequestInfo | InsufficientQuantity of RequestInfo
Conclusion
The source code to my implementation can be found on GitHub.