Stock Management with Power Apps, Power Automate and Power BI (Part II)

Dan Gray

2022/09/16

Premable

Here in Part II of the series on “Stock Management with Power Apps, Power Automate and Power BI” I build out a report to assess how groups of stocks have performed. Weekly stock-prices, financial filings, and volume traded are all incorporated.

Date Model

The main data is contained in two primary tables tsPrice and companyFiling. These hold the historical weekly prices, and the history of company filings. Centrally I have prospect which holds the core dimensions, and some supporting information such as Market, Invested (yes/no), as well some calculated columns such as History Captured, Lifetime Income, Lifetime Profit, and Utility etc.

Companies (tracked) is a helper table that returns the distinct intersection of companies with both company filing and weekly time-series data and thus relevant for surfacing completely in this report.

Price Evolution / Balance Sheet

The first page attempts to give an overview of temporal evolution of a company’s stock price, as well as present some key metrics.

# Change in Income
% Change Income = 

VAR _maxIn = CALCULATE(
        SUM(cr841_companyFiling[cr841_nincome]),
        FILTER(cr841_companyFiling,cr841_companyFiling[cr841_fiscaldateending]=MAX(cr841_companyFiling[cr841_fiscaldateending])
        )
    )

VAR _minIn = CALCULATE(
        SUM(cr841_companyFiling[cr841_nincome]),
        FILTER(cr841_companyFiling,cr841_companyFiling[cr841_fiscaldateending]=MIN(cr841_companyFiling[cr841_fiscaldateending])
        )
    )

VAR _diff = _maxIn-_minIn

VAR _bothNeg = IF(_maxIn<0 && _minIn <0 && _maxIn < _minIn,
    DIVIDE(_minIn,_maxIn,0)-1,
        IF(_maxIn<0 && _minIn <0 && _maxIn > _minIn,
            DIVIDE(_minIn,_maxIn,0)-1,
                DIVIDE(_diff,ABS(_minIn),0)
        )
)

RETURN
_bothNeg

A normalized stock price (NSP) is used to compare company-specific performance to the overall performance of all the entire portfolio, i.e. all companies.

Two chained sets of measures are used, and the normalized price;

NSP Company/All <- Mean Stock Price <- Normalized Stock Price

# NSP (Company)
YTD MSP (COMPANY) = 
    TOTALYTD(cr841_companyFiling[Mean Stock Price (COMPANY)],CalAuto[Date]
    )

# NSP (All)
YTD MSP (ALL) = 
  CALCULATE(TOTALYTD(cr841_companyFiling[Mean Stock Price (ALL)],CalAuto[Date]),
            REMOVEFILTERS(cr841_prospect)
  )

# Mean Stock Price (Company)
Mean Stock Price (COMPANY) = 
  AVERAGE(cr841_tsPrice[Normalized Price])

# Mean Stock Price (All)
Mean Stock Price (ALL) = 
    CALCULATE(AVERAGE(cr841_tsPrice[Normalized Price]),
    REMOVEFILTERS(cr841_prospect)
  
# Normalized Price
Normalized Price = 
  VAR MinOfGroup = CALCULATE(MIN(cr841_tsPrice[cr841_wprice]),
                             ALLEXCEPT(cr841_tsPrice,cr841_tsPrice[cr841_symbol]))
  VAR MaxOfGroup = CALCULATE(MAX(cr841_tsPrice[cr841_wprice]),
                             ALLEXCEPT(cr841_tsPrice,cr841_tsPrice[cr841_symbol]))
  VAR DetailValue = cr841_tsPrice[cr841_wprice]
RETURN DIVIDE(DetailValue - MinOfGroup,MaxOfGroup - MinOfGroup,0))

Performance / Grouping

Page 2 of the report delves into comparing stocks within groups, established via Price Buckets and a so-called Utility Group.

The Utility Group is a helper table defining the ranges which group like companies, defined by a lower and upper bound as well as the group-name;

Lifetime Utility calculates the ratio of Lifetime Income to Lifetime Profit.

Utility = 

IF(cr841_prospect[Lifetime Income]<0 && cr841_prospect[Lifetime Profit] <0,
DIVIDE(cr841_prospect[Lifetime Income],cr841_prospect[Lifetime Profit],0)*-1,
DIVIDE(cr841_prospect[Lifetime Income],cr841_prospect[Lifetime Profit],0)
)

If Utility was a measure instead of a calculated column Utility Group thresholds MinR, and MaxR would not only calculate the Utility Total Net Income but dynamically assign the group as the Utility shifted through the range of values.

Utility Total Net Income = 
CALCULATE([Total Net Income],
    FILTER(VALUES(cr841_prospect[Utility]),
    COUNTROWS(
        FILTER('Utility Group',
            cr841_prospect[Utility] >= MIN('Utility Group'[MinR]) &&
            cr841_prospect[Utility] < MAX('Utility Group'[MaxR])
            )) >0
    )
)

Volume / Industry

On Page 3, the Weekly Volume and YTD Volume are visualized in various forms. I use Price Buckets again to group stocks per default, but one can slice by Market too.

These primary visual serves as a quick way to notice sudden surges or drops in volume traded (retrospectively) and would be the basis for further dedicated research over the periods of interest.