Skip to main content

Asset Pricing Revised

 In a previous post I included a problem definition and an example. Upon reviewing the post, I discovered that not only were both the problem definition and the example incorrect, they were inconsistent. In this post I aim to correct the problem definition and then reimplement the example with the new problem definition.


Part 1: Correct the Problem Definition

Let's start with a recap of the problem we are trying to solve. Note that I altered the problem definition a bit to correct two major issues:

  1. The constraint in the original problem definition was not in terms of the givens. The requirement is that the expression we optimize must be expressible entirely in the terms we are given. A previous definition omitted the payouts and the potential economic scenarios, both of which are referenced in our constraint equation.

  2. Optimization was not with respect to the correct property. The original expression I defined was optimizing payout minus the cost of the portfolio. But we are not optimizing for dollars at time T, we are optimizing for total utility after T timesteps. Not only does that mean we must convert dollars to utility in this expression but we must consider the utility of consumption both today and at the liquidation period, not just at the liquidation period.


The new problem definition corrects both of these issues by augmenting the list of given quantities and rewriting the objective function in terms of collective utility.  


Portfolio Problem Definition

Given

  1. M assets, where pi,t is the price per unit at time t

  2. an initial investment amount N in today dollars

  3. Time step length (day, month, year)

  4. A liquidation period T (how many steps into the future we want to liquidate)

  5. A set of potential economic outcomes {ω1 … ωS} and the probability of each occurring Pr(ωi)

  6. A payout for each asset i under each potential outcome ω after time T, written as Xi,t+T(ω)

  7. Income WTs) at time T. This value represents external income from employer or government payout like social security. It is dependent on the economic outcome.

  8. Relative risk aversion 𝛾

  9. Subjective discount factor 𝛽 


Unknown: A portfolio P := {q1, …, qm } where qi is the number of units of asset i held in the portfolio.


Constraint


We want to find P* such that the expected utility of consumption is maximized. The total expected utility of consumption can be expressed as the amount of utility consumed today plus the amount of utility consumed after liquidation. 


Let U(P) be the expected utility of consumption of a portfolio P. We define it as


U : P → ℝ where P := ℕM

q1, …, qm → utility of today’s consumption + utility of future consumption

q1, …, qm → u(ct) + 𝛽 SUMs=1..S Pr(ωs)u(ct+Ts))                                         [1.1]


Where ct is the consumption today. We consume whatever we decided to not spend on investment so

 

ct = TotalFunds - cost of portfolio

ct = N - SUMi=1..M qi pi,t                                                                 [1.2]


The amount consumed T steps from now is written as ct+Ts). Note that we have a dependence on the outcome because the future is uncertain and the payout of a particular asset is dependent on the economic conditions. The amount consumed T steps from now is the payout from our investments T steps from now (i.e. assume we liquidate at time t+T). 


ct+Ts) = WTs) + SUMi=1..M qi Xi, t+Ts)                                                 [1.3]


Recall from the previous post that the utility function u is defined as 


u : (0, ∞) → ℝ

c c1-𝛾/(1 - 𝛾)                                                                         [1.4]


If we assume a relative risk aversion of 𝛾 = 1 we can reduce [1.4] to 


c log(c)                                                                              [1.5]


Substituting [1.2] through [1.5] into [1.1] gives us


q1, …, qm → log(N - SUMi=1..M qi pi,t) + 𝛽 SUMs=1..S Pr(ωs)log(WTs) +SUMi=1..M qi Xi, t+Ts))     [1.6]


Framing [1.6] as an optimization problem gives us


P* = maxq[1]…q[m] log(N - SUMi=1..M qi pi,t) + 𝛽 SUMs=1..S Pr(ωs)log(WTs) +SUMi=1..M qi Xi, t+Ts))   [1.7]


Subject to the constraints 


SUMi=1…M qi pi,t < N                                                                         [1.8a]

SUMi=1..M qi Xi, t+Ts) > 0   ∀ωs                                                             [1.8b]

WTs) > 0              ∀ωs                                                             [1.8c]


Note that the constraint [1.8b] is purely mathematical because we defined the utility function as log(c) and log is undefined in the negative domain.


Part 2: Correct the Example

Now let's look at the example that was provided. I have revised the definition so that it complements the above problem definition.


Portfolio Problem Instance

Given

  1. 3 assets (Defensive, cyclic, offensive), each priced at $100

  2. an initial investment amount $1000 in today dollars

  3. Time units size month

  4. Liquidation period 1 time step away (T = 1)

  5. 3 potential outcomes: boom, recession, normal, each with an equal probability of occurrence 1/3.

  6. Payouts for each asset under each outcome: 

    1. Defensive={boom:102,recession:104,normal:101} 

    2. Cyclic={boom:104,recession:95,normal:102} 

    3. Offensive={boom:110,recession:90,normal:103}

  7. Income is zero. We assume this is a retiree.

  8. Gamma is 1

  9. 𝛽 is 0.99 (this was not specified in the original example because it was baked into the SDF. I am taking the opportunity to correct this and calculate the SDF instead of providing it for each outcome)


The example from the first blog post tried to solve this problem instance but it did not solve this problem instance by optimizing [1.7] from the corrected problem definition. Let us do that now.


We want to maximize [1.7] subject to [1.8a], [1.8b], and [1.8c] with the values in the Problem Instance box. To constrain [1.7] to our budget we employ the method of Lagrange multipliers. From source [1], 


The method of Lagrange multipliers

Let’s suppose we want to find the local max or min of a function f : R2 → R. For unconstrained cases, we find the maximum or minimum by setting the derivative to zero and solving for the point values. For example the local maximum of a concave surface will look like the following:


Photo credit: source [2]. The max is the coordinate colored yellow on the plane where the derivative of the function is zero.


It is often the case that we want to find the local max or min of a surface subject to some constraint. For example, consider a similar surface except that we want to find the minimum subject to the boundary defined by the level surface in green. Now the minimum occurs not where the derivative is zero but at the boundary of our constraint, as illustrated below.

Photo credit: source [2]. Note that the level surface in green defines a set of points D = {[x,y] : g([x,y]) = c} and the boundary of the surface is defined by {z : z = f([x,y]) where [x,y] in D}


That is, if we have a problem where we want to find the max or min of a surface subject to a constraint we have to check not only the “flat” points, but the points along the boundary.


Note that this is exactly the problem we are trying to solve with our portfolio optimizer. We want to find the optimal allocations but we are constrained by our budget.


Now that the motivation is clear, let’s define the problem more formally.


Lagrange Problem Definition

Given: 

  1. A target function f: RN → R

  2. A constraint function g: RN → R 

  3. A constant c that gives the feasible set D with respect to g, defined D = {[x1,...,xN] : g([x1,...,xN]) = c}


Unknown:

A set of points P subset RN 


Constraint:

If p in P then p is a local minimum or local maximum of f and g(p) = c


How can we solve for P? We will solve for P by finding a relationship between ▽g and ▽f at max/min points on the boundary that is equivalent to the criterion that the max/min points on the boundary are where the derivative of the boundary is zero. 


To do this we will express the boundary with a parameterization and identify (1) a relationship between ▽f and the parameterization at max/min points (2) a relationship between ▽g and the parameterization at max/min points (3) reduce (1) and (2) to a relationship between ▽g and ▽f by removing the intermediary parameterization. 


Let x(t) = [x1(t), …, xN(t)] parameterize the feasible set in RN where g(x(t)) = c for all t. We can visualize the boundary by introducing a lifted parameterization C : R → RN x R defined by t → [[x1(t), …, xN(t)], f([x1(t), …, xN(t)])] where g([x1(t), …, xN(t)]) = c. The second term in the image of C are the boundary points on F.


x is the parameterization I mentioned that will enable us to define an expression we can evaluate to find the objective set of points P by relating ▽f to ▽g. 


Let P0 denote the point x(t0) on the boundary where the extrema occurs. Then


  • f at P0 is orthogonal to the velocity vector at t0 defined by dx(t0)/dt  [2.1]

  • g at P0 is orthogonal to the velocity vector at t0 defined by dx(t0)/dt  [2.2]

  • Thus, ▽f and ▽g are parallel and we can write ▽f = λ▽g where λ is a scalar multiplier.


Let us informally explain why [2.1] is true. We say two vectors x and y are orthogonal if xᐧy = 0. To show ▽f(P0) is orthogonal to the velocity vector dx(t0)/dt then we must write out the dot product and show that it must be zero if P0 is a max/min of f at the boundary. 


We can write ▽f(P0) as 


[∂f(P0)/∂x1 … ∂f(P0)/∂xN]


We can write the velocity vector dx(t0)/dt as


[dx1(t0)/dt … dxN(t0)/dt]


And the dot product is


f(P0) . dx(t0)/dt = ∂f(P0)/∂x1 * dx1(t0)/dt + … + ∂f(P0)/∂xN * dxN(t0)/dt

                  = df(P0)/dt = d/dt [f(x(t))] |t=t0


The extrema of f along the boundary occur at df(x(t))/dt = ▽f(x(t)) * dx(t)/dt = 0. Thus if P0 is an extrema at the boundary then df(P0)/dt = 0 which means that ▽f(P0) . dx(t0)/dt = 0 which means that the gradient of f is orthogonal to the velocity vector of x at local extrema.


Now let us show why [2.2] is true. We can write ▽g(P0) as


[∂g(P0)/∂x1 … ∂g(P0)/∂xN]


And the dot product with dx(t0)/dt is


g(P0) . dx(t0)/dt = ∂g(P0)/∂x1 * dx1(t0)/dt + … + ∂g(P0)/∂xN * dxN(t0)/dt

                  = dg(P0)/dt = d/dt [g(x(t))] |t=t0


Note that because 


g(P0) = c

dg(P0)/dt = 0


And thus ▽g(P0) is also orthogonal to dx(t0)/dt. 


This leads us to conclude that ▽f and ▽g are parallel and we can write ▽f = λ▽g where λ is a scalar multiplier.


Now that we have a method for solving for the optimal portfolio, let’s apply this method to our particular problem instance. 


Method of Lagrange Multipliers Applied to Portfolio Problem Instance

Given:

  1. A target function f([q1, …, qM]) = log(N - SUMi=1..M qi pi,t) + 𝛽 SUMs=1..S Pr(ωs)log(WTs) +SUMi=1..M qi Xi, t+Ts)) [3.1]

  2. A constraint function g([q1, …, qM]) = SUMi=1…M qi pi,t

  3. A constant N that gives the feasible set D with respect to g, defined D = {[q1, …, qM] : g([q1, …, qM]) = c}

Unknown:

A portfolio allocation P* = [q1, …, qM]


Constraint:

P* maximizes [3.1] subject to g(P*) = N. Note that we will assume that the investor invests everything. Otherwise this would be a less than or equal to relationship.



In the blue box we learned we can solve for P* by solving for P in the expression f(P) = λ▽g(P) subject to g(P) = N. Let us write this out in terms of our portfolio functions to derive the system of equations to solve:


Derive our System of Equations

f(P) = λ▽g(P)

f(P) - λ▽g(P) = 0


∂/∂q[i](log(N-Σi=1..M qi pi,t)+ 𝛽 Σs=1..SPr(ωs)log(WTs)+Σi=1..M qi Xi,t+Ts))) - λ(∂/∂q[i](SUMi=1…M qi pi,t) = 0 for i=1,2,3


∂/∂q[i](log(N-Σi=1..M qi pi,t)) + ∂/∂q[i](𝛽 Σs=1..SPr(ωs)log(WTs)+Σi=1..M qi Xi,t+Ts)))) - λpi,t = 0 for i=1,2,3


-pi,t/(N-Σi=1..M qi pi,t) + 𝛽 Σs=1..SPr(ωs) Xi,t+Ts)/(WTs)+Σi=1..M qi Xi,t+Ts))) - λpi,t = 0 for i=1,2,3


Rewriting this as a single summation over states gives us 


 𝛽 Σs=1..SPr(ωs) Xi,t+Ts)/(WTs)+Σi=1..M qi Xi,t+Ts))) - pi,t (λ + 1/(N-Σi=1..M qi pi,t)) = 0 for i=1,2,3                 [3.2]


Subject to


SUMi=1…M qi pi,t = N                                                                                           [3.3]


After this derivation we have both (a) the system of equations we need to solve and (b) the parameter values (the payouts and outcome probabilities). We can use a simple root finder numerical method to solve this. The root finder will start at a point and iteratively update that point until the image of that point under the system of equations is zero. Let’s dive into the code used to solve this 


Solve our System of Equations

Root Finder. Given a system of equations F: RN → RM and a starting point q0, the root finder will find the point q in RN s.t. F(q) = 0. Note that here, F is the residual function. In this case, the residual is [3.2] and [3.3].


Note that this is a slow simplistic algorithm but for our toy problem it is a reasonable solution. 


using Vec = std::vector<float>;

Vec rootSolver(

       std::function<Vec(const Vec&)> residual,

       Vec const& q0,

       int maxIter = 1000,

       float tol = 1e-6f,

       float learningRate = 0.001f

) {

   Vec q = q0;

   for (int iter = 0; iter < maxIter; ++iter) {

       Vec f = residual(q);

       float maxStep = 0.0f;

       for (size_t i = 0; i < q.size(); ++i) {

           float qi = q[i] - learningRate * f[i]; 

           if (qi < 0.0f) qi = 0.0f;

           q[i] = qi;

           if (std::abs(learningRate * f[i]) > maxStep) maxStep = std::abs(learningRate * f[i]);

       }

       if (maxStep < tol) break;

   }

   return q;

}



Parameter Values. We need to define our payouts and economic scenarios. I have provided these values through a Scenario and Asset class. A model is a function that accepts a set of assets and returns a list of scenarios. The model’s job is to determine what the payout of each asset is under different economic scenarios. 



enum Type {

   Cyclic, Defensive, Offensive

};


struct Asset {

   int ID;

   /// The price of one share today

   float cost;

   /// abstraction of features that might be used to determine

   /// payout under different economic environments

   Type type;

};


struct Scenario {

   std::string name;

   float probability;

   std::vector<Asset> assets;


   /// TODO: do not hard code these

   float cyclicGrowth;

   float defensiveGrowth;

   float offensiveGrowth;


   /// What is the payout for this asset after

   /// T timesteps under this economic scenario

   float payout(int T, int ID) const;

};


struct Portfolio {

   /// The amount of units of each asset held in this portfolio

   std::unordered_map<int, int> assets;


   void dump() const;

};



template<size_t N>

class WorldGenerator {

public:

   virtual std::array<Scenario, N> generate(std::vector<Asset> const& assets, int T) = 0;

   virtual float subjectiveDiscountFactor() const = 0;

};


/// Mock up values for the world. Real models will not hard code the scenarios 

/// but generate them based on patterns learned from historical scenarios

class MockGenerator : public WorldGenerator<3> {

public:

   std::array<Scenario, 3> generate(std::vector<Asset> const& assets, int T) override;

   float subjectiveDiscountFactor() const override;

};


// implementation

float MockGenerator::subjectiveDiscountFactor() const {

   return 0.99f;

}


std::array<Scenario, 3> MockGenerator::generate(std::vector<Asset> const& assets, int T) {

   std::array<Scenario, 3> scenarios;

   // recession

   Scenario& recession = scenarios[0];

   recession.name = "Recession";

   recession.probability = 0.33f;

   recession.defensiveGrowth = 0.04;

   recession.cyclicGrowth = -0.05;

   recession.offensiveGrowth = -0.10;

   recession.assets = assets;


   // normal

   Scenario& normal = scenarios[1];

   normal.name = "Normal";

   normal.probability = 0.34f;

   normal.assets = assets;

   normal.defensiveGrowth = 0.01;

   normal.cyclicGrowth = 0.02;

   normal.offensiveGrowth = 0.03;


   // boom

   Scenario& boom = scenarios[2];

   boom.name = "Boom";

   boom.probability = 0.33f;

   boom.assets = assets;

   boom.defensiveGrowth = 0.02;

   boom.cyclicGrowth = 0.05;

   boom.offensiveGrowth = 0.10;


   return scenarios;

}


float Scenario::payout(int T, int ID) const {

   Asset const& asset = this->assets[ID];

   float growth;

   switch(asset.type){

       case Cyclic:

           growth = cyclicGrowth;

           break;

       case Defensive:

           growth = defensiveGrowth;

           break;

       case Offensive:

           growth = offensiveGrowth;

           break;

   }

   float payout = asset.cost * std::pow(1.0f + growth, static_cast<float>(T));

   return payout;

}


void Portfolio::dump() const {

   for(auto asset : assets){

       printf("%d : %d, ", asset.first, asset.second);

   }

   printf("\n");

}



Build Scenarios and Find Portfolio. We will visit the portfolio finder next, but first let us set up the main function that binds the parameter values with the system of equations.


int main() {

   std::vector<Asset> assets(3);

   assets[0] = Asset{0, 100.0, Type::Defensive};

   assets[1] = Asset{1, 100.0, Type::Cyclic};

   assets[2] = Asset{2, 100.0, Type::Offensive};

   int months = 1;

   float budget = 1000;

   MockGenerator mockGenerator;

   Portfolio portfolio = portfolioFinder(&mockGenerator, budget, assets, months);

   portfolio.dump();

   return 0;

}


 

Portfolio Finder. We need to define F and q0 correctly in order to call the rootSolver. The following function does just that. But it blows up (this is why I colored the box red; do not use this code)! How can we fix it? Here is the original code that implements our analysis: 


  1. template<size_t N>

  2. Portfolio portfolioFinder(WorldGenerator<N>* worldGenerator, float budget, std::vector<Asset> const& assets, int T){

  3.    /// Create system of equations.

  4.    /// Let N be the number of scenarios

  5.    /// Let M be the number of assets

  6.    /// Then we have M+1 equations, one for each asset and one to constrain the portfolio to the budget

  7.    /// Each equation: 

  8.    /// del L/ del q[i] 

  9.    /// = SUM[s=1..N](Prob(s) * payout(asset[i], s)/(SUM[j=1..M](q[j]* payout(asset[j], s)) - p[i](lambda + 1/(N-Σi=1..M q[i] p[i])) = 0

  10.    /// Solve systems of equations.

  11.    auto worlds = worldGenerator->generate(assets, T);

  12.    float beta = worldGenerator->subjectiveDiscountFactor();

  13.    size_t M = assets.size();


  14.    // Function returning residual vector for FOC + budget

  15.    auto FOC = [&](const Vec& q) -> Vec {

  16.        // initialize the residual to M slots (one for each asset) plus the budget constraint

  17.        Vec residual(M + 1, 0.0f);

  18.        float lambda = q[M];


  19.        // compute the denominator of the second term, N-Σi=1..M q[i] p[i]

  20.        float sum = 0.0f;

  21.        for (size_t j = 0; j < M; ++j)

  22.            sum += q[j] * assets[j].cost;

  23.        float denominatorCostTerm = std::max(1e-6f, budget - sum);


  24.        // Compute residual for each asset i=1..M

  25.        for (size_t i = 0; i < M; ++i) {

  26.            // Compute Pr(ωs) X[i,t+T](ωs)/(WT(ωs)+Σi=1..M qi Xi,t+T(ωs)))

  27.            float expectation = 0.0f;

  28.            for (auto const& economicScenario : worlds) {

  29.                float denominator = 0.0f;

  30.                for (size_t j = 0; j < M; ++j)

  31.                    denominator += q[j] * economicScenario.payout(T, j);

  32.                denominator = std::max(1e-3f, denominator);

  33.                expectation += economicScenario.probability * economicScenario.payout(T, i) / denominator;

  34.            }

  35.            // beta * SUM[ωs] Pr(ωs) X[i,t+T](ωs)/(WT(ωs)+Σi=1..M qi X[i,t+T](ωs))) - p[i](lambda + 1/(N-Σi=1..M q[i] p[i]))

  36.            residual[i] = beta * expectation - assets[i].cost * (lambda + 1 / denominatorCostTerm);

  37.        }


  38.        // Budget equation as last entry

  39.        float totalCost = 0.0f;

  40.        for (size_t i = 0; i < M; ++i)

  41.            totalCost += q[i] * assets[i].cost;

  42.        residual[M] = totalCost - budget;


  43.        return residual;

  44.    };


  45.    // Initial guess (equal allocation)

  46.    Vec qmu(M + 1, budget / (M * assets[0].cost));

  47.    qmu[M] = 0.01; // initial mu


  48.    // Solve

  49.    Vec solution = rootSolver(FOC, qmu);


  50.    // Build portfolio

  51.    Portfolio p;

  52.    for (size_t i = 0; i < M; ++i)

  53.        p.assets[assets[i].ID] = static_cast<float>(solution[i]);


  54.    return p;

  55. }



Note the highlighted terms. I prevented the denominators from becoming zero, but even if they are not zero and very small, that highlighted term on line 39 is going to blow up. A huge residual will prevent the root solver from converging.


This term came from the logarithmic utility term log(N-Σi=1..M qi pi,t). This represents the utility gained from consuming the leftover funds after purchasing our portfolio. But if we spend everything on our portfolio then this term is zero and the log of 0 is undefined. To solve this, we can say that we have already set aside funds necessary to maintain the lifestyle today and omit that term entirely.



template<size_t N>

Portfolio portfolioFinder(WorldGenerator<N>* worldGenerator, float budget, std::vector<Asset> const& assets, int T){

   /// Create system of equations.

   /// Let N be the number of scenarios

   /// Let M be the number of assets

   /// Then we have M+1 equations, one for each asset and one to constrain the portfolio to the budget

   /// Each equation: del L/ del q[i] = SUM[s=1..N](Prob(s) * payout(asset[i], s)/(SUM[j=1..M](q[j]* payout(asset[j], s)) - lambda * cost(i) = 0

   /// Solve systems of equations.

   auto worlds = worldGenerator->generate(assets, T);

   float beta = worldGenerator->subjectiveDiscountFactor();

   size_t M = assets.size();


   // Function returning residual vector for FOC + budget

   auto FOC = [&](const Vec& q) -> Vec {

       // initialize the residual to M slots (one for each asset) plus the budget constraint

       Vec residual(M + 1, 0.0f);

       float lambda = q[M];


       // Compute residual for each asset i=1..M

       for (size_t i = 0; i < M; ++i) {

           // Compute Pr(ωs) X[i,t+T](ωs)/(WT(ωs)+Σi=1..M qi Xi,t+T(ωs)))

           float expectation = 0.0f;

           for (auto const& economicScenario : worlds) {

               float denominator = 0.0f;

               for (size_t j = 0; j < M; ++j)

                   denominator += q[j] * economicScenario.payout(T, j);

               denominator = std::max(1e-3f, denominator);

               expectation += economicScenario.probability * economicScenario.payout(T, i) / denominator;

           }

           // beta * SUM[ωs] Pr(ωs) X[i,t+T](ωs)/(WT(ωs)+Σi=1..M qi X[i,t+T](ωs))) - p[i](lambda)

           residual[i] = beta * expectation - assets[i].cost * lambda;

       }


       // Budget equation as last entry

       float totalCost = 0.0f;

       for (size_t i = 0; i < M; ++i)

           totalCost += q[i] * assets[i].cost;

       residual[M] = totalCost - budget;


       return residual;

   };


   // Initial guess (equal allocation)

   Vec qmu(M + 1, budget / (M * assets[0].cost));

   qmu[M] = 0.01; // initial mu


   // Solve

   Vec solution = rootSolver(FOC, qmu);


   // Build portfolio

   Portfolio p;

   for (size_t i = 0; i < M; ++i)

       p.assets[assets[i].ID] = static_cast<float>(solution[i]);


   return p;

}



The revised code will find the uninteresting solution of purchasing three units of each asset, or [3,3,3].


I hope you enjoyed this walkthrough of how to find the optimal portfolio with respect to utility. Perhaps in a future entry I will productize the toy by improving the numerical solver and wiring in Bayesian models for the world generator. 

Sources:

[1] Thomas calculus 11th addition, 2005

[2] https://www.youtube.com/watch?v=5A39Ht9Wcu0 

[3] My last blog post, which relied on Asset Pricing, John H. Cochrane, 2001

Comments

Popular posts from this blog

Model Based Public Policy: Bayesian Neural Nets and Trade

  1 Motivation Recent changes in U.S. trade policy have sparked debates about their effect on American prosperity, national security, and federal income. This work began as an attempt to formalize the relationship between trade policy and prosperity and continues as a journey learning data science and how to apply modeling to public policy. For my first installment, I recount my experience building a Bayesian neural net to solve the problem of selecting a trade policy that optimizes American prosperity. 2 Defining The Problem Let’s begin by defining the problem we wish to solve. Problem definitions require identifying three things:  Given information. This is the data we start with before any computation. For our modeling problem, we will need at a minimum data relevant to the prosperity of a nation along with trade policy data.  Unknown. The unknown is the set of values you want to identify. For a modeling problem, the unknown are  The templated function we wish to...

Back To Basics: An Introduction to Bayesian Modeling

I was preparing another blog series on how transformers work when the real world disrupted my focus and once again pulled me into the world of Bayesian inference. This time it was not geopolitical tensions that caught my attention but the relentless ascent of the S & P 500 despite the perceptively turbulent social and political environment of the United States. Confused, I sought answers by trying to identify relationships between economic activity and the share price of the largest American corporations. Assuming this relationship to be noisy, I reached for the tool that would quantify this noise via reported uncertainty. This blog post is a recount of this journey, starting with a review of the tools I plan to use to learn the relationships of interest.  This first installment is introductory. A subsequent post will attempt to recreate the results of a decades old case study with modern data. In a final installment, a hypothesis about relationships in modern times will be pro...