Building a Trading App (Xamarin.Forms & F#)

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.

3 Replies to “Building a Trading App (Xamarin.Forms & F#)”

Leave a reply to Charles Robbins Cancel reply