diff --git a/docs/index.rst b/docs/index.rst index 134d8cf..a49d46b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -94,10 +94,14 @@ We can easily add a tax to the market and visualize its effects. # Apply a $2.50 tax market.tax = 2.5 + print(f"Consumer burden: ${market.consumer_tax_burden:.2f}") + print(f"Producer burden: ${market.producer_tax_burden:.2f}") + print(f"Consumer share: {market.consumer_tax_share:.0%}") market.plot(surplus=True) This plot shows the higher price paid by consumers, the lower price -received by producers, and the shaded tax revenue rectangle. +received by producers, and the shaded tax revenue rectangle. The burden +properties show how much of the tax is borne by each side of the market. .. image:: _static/tax_example_plot.svg :alt: Market plot with tax revenue shaded diff --git a/freeride/equilibrium.py b/freeride/equilibrium.py index 5665a8f..dda1ee2 100644 --- a/freeride/equilibrium.py +++ b/freeride/equilibrium.py @@ -247,6 +247,38 @@ def govt_revenue(self): def govt_expenditure(self): return -self.govt_revenue + @property + def tax_wedge(self): + return self.__p_consumer - self.__p_producer + + @property + def consumer_tax_burden(self): + if self.__tax == 0: + return 0.0 + return self.__p_consumer - self._free_market_price() + + @property + def producer_tax_burden(self): + if self.__tax == 0: + return 0.0 + return self._free_market_price() - self.__p_producer + + @property + def consumer_tax_share(self): + if self.__tax == 0: + return 0.0 + return self.consumer_tax_burden / self.__tax + + @property + def producer_tax_share(self): + if self.__tax == 0: + return 0.0 + return self.producer_tax_burden / self.__tax + + def _free_market_price(self): + p_star, _ = self._find_intersection(self.demand, self.supply) + return p_star + @property def total_surplus(self): return ( diff --git a/tests/test_equilibrium.py b/tests/test_equilibrium.py index 343d76d..bbc6bd5 100644 --- a/tests/test_equilibrium.py +++ b/tests/test_equilibrium.py @@ -32,3 +32,46 @@ def test_nonbinding_floor(self): self.assertEqual(eq_floor.q, self.equilibrium.q) if hasattr(eq_floor, "excess_supply"): self.assertEqual(eq_floor.excess_supply, 0) + + def test_symmetric_tax_incidence(self): + demand = Demand(10, -1) + supply = Supply(0, 1) + eq_tax = Equilibrium(demand, supply, tax=2) + self.assertAlmostEqual(eq_tax.tax_wedge, 2.0) + self.assertAlmostEqual(eq_tax.consumer_tax_burden, 1.0) + self.assertAlmostEqual(eq_tax.producer_tax_burden, 1.0) + self.assertAlmostEqual(eq_tax.consumer_tax_share, 0.5) + self.assertAlmostEqual(eq_tax.producer_tax_share, 0.5) + + def test_inelastic_demand_places_more_tax_burden_on_consumers(self): + demand = Demand(10, -4) + supply = Supply(0, 1) + eq_tax = Equilibrium(demand, supply, tax=2) + self.assertAlmostEqual(eq_tax.consumer_tax_burden, 1.6) + self.assertAlmostEqual(eq_tax.producer_tax_burden, 0.4) + self.assertGreater( + eq_tax.consumer_tax_share, + eq_tax.producer_tax_share, + ) + self.assertAlmostEqual( + eq_tax.consumer_tax_share + eq_tax.producer_tax_share, + 1.0, + ) + + def test_no_tax_has_zero_tax_incidence(self): + self.assertAlmostEqual(self.equilibrium.tax_wedge, 0.0) + self.assertAlmostEqual(self.equilibrium.consumer_tax_burden, 0.0) + self.assertAlmostEqual(self.equilibrium.producer_tax_burden, 0.0) + self.assertAlmostEqual(self.equilibrium.consumer_tax_share, 0.0) + self.assertAlmostEqual(self.equilibrium.producer_tax_share, 0.0) + + def test_subsidy_incidence_uses_signed_burdens(self): + demand = Demand(10, -1) + supply = Supply(0, 1) + eq_subsidy = Equilibrium(demand, supply) + eq_subsidy.subsidy = 2 + self.assertAlmostEqual(eq_subsidy.tax_wedge, -2.0) + self.assertAlmostEqual(eq_subsidy.consumer_tax_burden, -1.0) + self.assertAlmostEqual(eq_subsidy.producer_tax_burden, -1.0) + self.assertAlmostEqual(eq_subsidy.consumer_tax_share, 0.5) + self.assertAlmostEqual(eq_subsidy.producer_tax_share, 0.5)