Intro
This is my fourth post on my journey of building a trading app using F# and Xamarin.Forms. In my last post, I brain-dumped XAML and F# for selling shares of stock. In this post, I will brain-dump the syntax for buying shares of stock for both the UI and viewmodels.
Solution Explorer
I modeled my solution explorer to reflect my interpretation of Clean Architecture:
UI
The user-experience for buying shares is the following:
XAML
Here’s the XAML for the Buy page:
<?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.BuyPage"> <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='Buying ({0:F0})'}}" FontSize="Large" /> <Label Grid.Row="2" Grid.Column="0" Text="{Binding StockPrice, StringFormat='{}{0:c} per share'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" /> <Label Grid.Row="3" Grid.Column="0" Text="{Binding Total, StringFormat='{}{0:c} Invested'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" /> <Label Grid.Row="4" Grid.Column="1" Text="{Binding Balance, StringFormat='{}{0:c} balance'}}" FontSize="Large" HorizontalOptions="Start" /> <Entry Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding BuyQty, Mode=TwoWay}" Placeholder="Enter number of shares to buy" HorizontalTextAlignment="Center" Keyboard="Numeric" /> <Label Grid.Row="7" Grid.Column="1" Text="{Binding BuyValue, StringFormat='{}{0:c}'}" HorizontalTextAlignment="End" /> <Button Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Text="Confirm" Command="{Binding Confirm}" IsEnabled="{Binding CanBuy}" /> </Grid> </ContentPage>
The code-behind of the Buy page subscribes to a confirmation-request event:
using ManageTrades.ViewModels; using Xamarin.Forms; namespace FnTrade { using static Core.Entities; using static Integration.Factories; public partial class BuyPage : ContentPage { public BuyPage(BuyViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; getDispatcher().ConfirmBuyRequested += async (s, e) => { var requestInfo = e as RequestInfo; var confirmed = await DisplayAlert("Confirmation", $"Buying ({(requestInfo).Quantity }) shares of {requestInfo.Symbol}?", "Confirm", "Cancel"); if (confirmed) getDispatcher().ExecuteBuy(requestInfo); }; } } }
When the purchase of shares is confirmed, the app presents a purchase request confirmation.
The following is the XAML for the purchase shares request confirmation:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="FnTrade.RequestBuyConfirmedPage"> <Grid Margin="10,5"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="Request Sent" FontSize="Large" TextColor="Green" FontAttributes="Bold" HorizontalOptions="Center" /> <StackLayout Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalOptions="Center"> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding Quantity, StringFormat='Buy ({0:F0}) shares of'}}" FontSize="Medium" HorizontalOptions="Center" HorizontalTextAlignment="Center" /> <Label Text="{Binding Symbol}" FontSize="Medium" HorizontalOptions="Start" /> </StackLayout> <Label Grid.Row="2" Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:c}'}}" FontSize="24" FontAttributes="Bold" HorizontalOptions="End" /> </StackLayout> </Grid> </ContentPage>
ViewModels
Here’s the viewmodel for buying shares:
namespace ManageTrades.ViewModels open System open System.Windows.Input open Core.IntegrationLogic open Core.Entities open Integration.Factories open Services type BuyViewModel(info:SharesInfo) as this = inherit ViewModelBase() let dispatcher = getDispatcher() let accountId = getAccountId() let broker = getBroker() :> IBroker let mutable buyQty = "" let mutable canBuy = false let mutable buyValue = 0m let confirm = DelegateCommand( (fun _ -> this.ConfirmBuy()) , fun _ -> true ) :> ICommand member this.Symbol with get() = info.Shares.Symbol member this.Balance with get() = info.Balance member this.StockPrice with get() = info.PricePerShare member this.Total with get() = ((decimal)info.Shares.Qty * info.PricePerShare) member this.BuyQty with get() = buyQty and set(value) = buyQty <- value let success , validQty = Int32.TryParse buyQty if success then this.CanBuy <- info.PricePerShare *(decimal)validQty <= this.Balance else this.CanBuy <- false this.UpdateBuyValue() base.NotifyPropertyChanged(<@ this.BuyQty @>) member this.BuyValue with get() = buyValue and set(value) = buyValue <- value base.NotifyPropertyChanged(<@ this.BuyValue @>) member this.CanBuy with get() = canBuy and set(value) = canBuy <- value base.NotifyPropertyChanged(<@ this.CanBuy @>) member this.Confirm = confirm member private this.ConfirmBuy() = if this.CanBuy then dispatcher.ConfirmBuy { AccountId = accountId Symbol = this.Symbol Quantity = Int32.Parse this.BuyQty } member private this.UpdateBuyValue() = let success , validQty = Int32.TryParse buyQty if success then this.BuyValue <- (decimal)validQty * info.PricePerShare else this.BuyValue <- 0m
Here’s the viewmodel for the purchase request confirmation:
namespace ManageTrades.ViewModels open Core.IntegrationLogic open Core.Entities open Services open Integration.Factories type RequestBuyConfirmedViewModel(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)
Domain Types
The following module reflects the entities that will be used in my trading app:
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 Balance : 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 for this app can be found here on GitHub.
Thank you for posting!