Skip to content

Common

Realizations shared by several economics systems: assets, balance sheets, exchange, forecasting, and resources.

AssetElement(label, visual=None, *, price=1.0)

Bases: Element

Traded asset described by its price.

Parameters:

Name Type Description Default
label str

The label of the asset.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
price Annotated[float, StateSpec(domain=open(0, oo))]

The initial price.

1.0

Examples:

>>> from skcomplex.systems.economics.finance import AssetElement
>>> AssetElement('stock', price=100.0).price
100.0
Source code in src/skcomplex/systems/economics/common/_asset.py
34
35
36
37
38
39
40
41
42
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    price: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.price = price

BalanceSheetElement(label, visual=VisualSpec('agents', {'color_by': 'status'}), *, external=0.0, equity=0.0, payment=0.0, status=SOLVENT)

Bases: Element

Bank of an interbank network, with its external assets, its equity, its payments, and its state of default.

The bank is a passive agent that the contagion of the system updates, a solvency cascade through its equity or a clearing of payments through its payment.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

VisualSpec('agents', {'color_by': 'status'})
external Annotated[float, StateSpec(domain=closed_open(0, oo))]

The external assets.

0.0
equity Annotated[float, StateSpec()]

The equity, the assets minus the liabilities.

0.0
payment Annotated[float, StateSpec(domain=closed_open(0, oo))]

The payment the bank makes on its interbank liabilities.

0.0
status Annotated[float, StateSpec(domain=NN)]

0 for solvent, 1 for defaulting in this step, and 2 for defaulted.

SOLVENT

Examples:

>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.economics.finance import BalanceSheetElement
>>> GroupElement('banks', element=BalanceSheetElement, size=3, values={'equity': 4.0}).size
3
Source code in src/skcomplex/systems/economics/common/_balance_sheet.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'status'}),
    *,
    external: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    equity: Annotated[float, StateSpec()] = 0.0,
    payment: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    status: Annotated[float, StateSpec(domain=NN)] = SOLVENT,
) -> None:
    super().__init__(label, visual=visual)
    self.external = external
    self.equity = equity
    self.payment = payment
    self.status = status

ExchangeInteraction(label, *, source, target, goods=GOODS, pairing='pairs', quantity=0.1, phase='trade')

Bases: TransferInteraction

Bilateral barter between paired traders, a transfer whose trades repeat while both traders gain.

Every step the transfer pairs the traders, and every pair barters small quantities of every pair of goods at the geometric mean of their marginal rates of substitution while both gain. A trader holds every good in the state of its name and its Cobb-Douglas weight in the state <good>_weight. Every trade moves goods between the two traders, so the total of every good stays the same, which the default run checks as invariants. The exchange reports the mean price of the second good in units of the first, the standard deviation dispersion of its logarithm over the trades of the step, and the number of trades volume.

Parameters:

Name Type Description Default
label str

The label of the exchange.

required
source str

The path of the group of traders.

required
target str

The path of the group of traders, the source again.

required
goods tuple[str, ...]

The names of the traded goods.

GOODS
pairing str

How the transfer pairs the traders, such as 'neighbor-pairs' for disjoint pairs of neighbours or 'pairs' for disjoint random pairs.

'pairs'
quantity float

The quantity of the bought good in every trade.

0.1
phase str

The phase the trades run in.

'trade'

Examples:

>>> from skcomplex.systems.economics.markets import ExchangeInteraction
>>> ExchangeInteraction('exchange', source='traders', target='traders').reports
('price', 'dispersion', 'volume')
Source code in src/skcomplex/systems/economics/common/_exchange_interaction.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(
    self,
    label: str,
    *,
    source: str,
    target: str,
    goods: tuple[str, ...] = GOODS,
    pairing: str = 'pairs',
    quantity: float = 0.1,
    phase: str = 'trade',
) -> None:
    super().__init__(
        label,
        source=source,
        target=target,
        state=goods[0],
        amount=quantity,
        pairing=pairing,
        form='agent',
        phase=phase,
    )
    self.goods = goods
    self.quantity = quantity

report_trades(system)

Report the mean price of the second good in the first, its dispersion, and the trades of the step.

Parameters:

Name Type Description Default
system SystemViewProtocol

The system view of the exchange.

required
Source code in src/skcomplex/systems/economics/common/_exchange_interaction.py
122
123
124
125
126
127
128
129
130
131
132
133
134
def report_trades(self, system: SystemViewProtocol) -> None:
    """Report the mean price of the second good in the first, its dispersion, and the trades of the step.

    Args:
        system:
            The system view of the exchange.
    """
    trades = system.storage.pop('trades', [])
    trades = trades if isinstance(trades, list) else []
    logs = [math.log(price) for bought, paid, price in trades if (bought, paid) == (0, 1)]
    system.report('price', math.exp(float(np.mean(logs))) if logs else math.nan)
    system.report('dispersion', float(np.std(logs)) if len(logs) > 1 else math.nan)
    system.report('volume', len(trades))

transfer(source, target, system)

Barter between two traders while both gain, and keep the trades of the step for its report.

Parameters:

Name Type Description Default
source AgentViewProtocol

The first trader.

required
target AgentViewProtocol

The second trader.

required
system SystemViewProtocol

The system view of the exchange.

required

Returns:

Name Type Description
traded bool

Whether the traders made a trade.

Source code in src/skcomplex/systems/economics/common/_exchange_interaction.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def transfer(self, source: AgentViewProtocol, target: AgentViewProtocol, system: SystemViewProtocol) -> bool:
    """Barter between two traders while both gain, and keep the trades of the step for its report.

    Args:
        source:
            The first trader.

        target:
            The second trader.

        system:
            The system view of the exchange.

    Returns:
        traded:
            Whether the traders made a trade.
    """
    weights = [f'{good}_weight' for good in self.goods]
    trades = trade_goods(source, target, self.goods, weights, quantity=self.quantity)
    logs = system.storage.setdefault('trades', [])
    if isinstance(logs, list):
        logs.extend(trades)
    return bool(trades)

GeometricBrownianMotionInteraction(label, *, target, mu=0.05, sigma=0.2, process=None, index=None)

Bases: RateInteraction

Geometric Brownian motion, dS = mu * S * dt + sigma * S * dW, of the price of an asset.

The Wiener process dW is independent by default, or process index of a WienerProcessSpec that correlates it with the motions of other assets.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the asset.

required
mu Annotated[float, ParameterSpec()]

The drift.

0.05
sigma Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The volatility.

0.2
process Annotated[WienerProcessSpec | None, RoleSpec(noise=True)]

The Wiener processes, one of which drives the price, or None for an independent process.

None
index int | None

The process of process that drives the price, or None without processes.

None

Examples:

>>> from skcomplex.systems.economics.finance import GeometricBrownianMotionInteraction
>>> from skcomplex.core.specs import WienerProcessSpec
>>> GeometricBrownianMotionInteraction('gbm', target='stock', sigma=0.3).sigma
0.3
>>> market = WienerProcessSpec('market', dimension=2)
>>> GeometricBrownianMotionInteraction('gbm', target='stock', process=market, index=1).index
1
Source code in src/skcomplex/systems/economics/common/_geometric_brownian_motion.py
51
52
53
54
55
56
57
58
59
60
61
62
63
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    mu: Annotated[float, ParameterSpec()] = 0.05,
    sigma: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    process: Annotated[WienerProcessSpec | None, RoleSpec(noise=True)] = None,
    index: int | None = None,
) -> None:
    super().__init__(label, target=target, state='price', process=process, index=index)
    self.mu = mu
    self.sigma = sigma

cobb_douglas(agent, goods, weights)

Return the logarithm of the Cobb-Douglas utility of an agent, sum_i a_i log x_i.

Parameters:

Name Type Description Default
agent object

The agent, with a state for the holding of every good and for the weight of every good.

required
goods Sequence[str]

The names of the states that hold the goods.

required
weights Sequence[str]

The names of the states that hold the weights of the goods, in the order of goods.

required

Returns:

Name Type Description
utility float

The logarithm of the utility, minus infinity when a weighted good runs out.

Examples:

>>> from types import SimpleNamespace
>>> from skcomplex.systems.economics.markets import cobb_douglas
>>> agent = SimpleNamespace(x=4.0, y=1.0, a=0.5, b=0.5)
>>> round(cobb_douglas(agent, ('x', 'y'), ('a', 'b')), 6)
0.693147
Source code in src/skcomplex/systems/economics/common/_exchange.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def cobb_douglas(agent: object, goods: Sequence[str], weights: Sequence[str]) -> float:
    """Return the logarithm of the Cobb-Douglas utility of an agent, `sum_i a_i log x_i`.

    Args:
        agent:
            The agent, with a state for the holding of every good and for the weight of every good.

        goods:
            The names of the states that hold the goods.

        weights:
            The names of the states that hold the weights of the goods, in the order of `goods`.

    Returns:
        utility:
            The logarithm of the utility, minus infinity when a weighted good runs out.

    Examples:
        >>> from types import SimpleNamespace
        >>> from skcomplex.systems.economics.markets import cobb_douglas
        >>> agent = SimpleNamespace(x=4.0, y=1.0, a=0.5, b=0.5)
        >>> round(cobb_douglas(agent, ('x', 'y'), ('a', 'b')), 6)
        0.693147
    """
    total = 0.0
    for good, weight in zip(goods, weights, strict=True):
        share, holding = float(getattr(agent, weight)), float(getattr(agent, good))
        if share == 0:
            continue
        if holding <= 0:
            return -math.inf
        total += share * math.log(holding)
    return total

marginal_rate(agent, good, numeraire, weights)

Return the marginal rate of substitution of a good for a numeraire, the units of numeraire one unit is worth.

Parameters:

Name Type Description Default
agent object

The agent.

required
good str

The name of the state that holds the good.

required
numeraire str

The name of the state that holds the numeraire.

required
weights tuple[str, str]

The names of the states that hold the weights of the good and of the numeraire.

required

Returns:

Name Type Description
rate float

The ratio of the marginal utilities, (a_good / x_good) / (a_numeraire / x_numeraire).

Examples:

>>> from types import SimpleNamespace
>>> from skcomplex.systems.economics.markets import marginal_rate
>>> agent = SimpleNamespace(x=2.0, y=6.0, a=0.5, b=0.5)
>>> marginal_rate(agent, 'x', 'y', ('a', 'b'))
3.0
Source code in src/skcomplex/systems/economics/common/_exchange.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def marginal_rate(agent: object, good: str, numeraire: str, weights: tuple[str, str]) -> float:
    """Return the marginal rate of substitution of a good for a numeraire, the units of numeraire one unit is worth.

    Args:
        agent:
            The agent.

        good:
            The name of the state that holds the good.

        numeraire:
            The name of the state that holds the numeraire.

        weights:
            The names of the states that hold the weights of the good and of the numeraire.

    Returns:
        rate:
            The ratio of the marginal utilities, `(a_good / x_good) / (a_numeraire / x_numeraire)`.

    Examples:
        >>> from types import SimpleNamespace
        >>> from skcomplex.systems.economics.markets import marginal_rate
        >>> agent = SimpleNamespace(x=2.0, y=6.0, a=0.5, b=0.5)
        >>> marginal_rate(agent, 'x', 'y', ('a', 'b'))
        3.0
    """
    good_weight, numeraire_weight = (float(getattr(agent, name)) for name in weights)
    return (good_weight / float(getattr(agent, good))) / (numeraire_weight / float(getattr(agent, numeraire)))

trade_goods(first, second, goods, weights, *, quantity=0.1, limit=1000)

Barter small quantities of goods between two agents while both gain, and return the trades.

For every pair of goods in turn, the agent that values the first good more in units of the second buys quantity of it from the other at the geometric mean of their marginal rates of substitution, as in the Sugarscape trade of Epstein and Axtell (1996). A trade happens only when it raises the Cobb-Douglas utility of both agents, and every trade is two transfers, so the total of every good is unchanged. The agents trade until a round over all pairs of goods makes no trade, or until limit trades.

Parameters:

Name Type Description Default
first object

The first agent, with a state for the holding and for the weight of every good.

required
second object

The second agent.

required
goods Sequence[str]

The names of the states that hold the goods.

required
weights Sequence[str]

The names of the states that hold the weights of the goods, in the order of goods.

required
quantity float

The quantity of the bought good in every trade.

0.1
limit int

The largest number of trades.

1000

Returns:

Name Type Description
trades list[tuple[int, int, float]]

The index of the bought good, the index of the good paid with, and the price of every trade.

Examples:

>>> from types import SimpleNamespace
>>> from skcomplex.systems.economics.markets import trade_goods
>>> first = SimpleNamespace(x=9.0, y=1.0, a=0.5, b=0.5)
>>> second = SimpleNamespace(x=1.0, y=9.0, a=0.5, b=0.5)
>>> trades = trade_goods(first, second, ('x', 'y'), ('a', 'b'), quantity=0.5)
>>> first.x + second.x, len(trades) > 0
(10.0, True)
Source code in src/skcomplex/systems/economics/common/_exchange.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def trade_goods(
    first: object,
    second: object,
    goods: Sequence[str],
    weights: Sequence[str],
    *,
    quantity: float = 0.1,
    limit: int = 1000,
) -> list[tuple[int, int, float]]:
    """Barter small quantities of goods between two agents while both gain, and return the trades.

    For every pair of goods in turn, the agent that values the first good more in units of the second buys `quantity`
    of it from the other at the geometric mean of their marginal rates of substitution, as in the Sugarscape trade of
    Epstein and Axtell (1996). A trade happens only when it raises the Cobb-Douglas utility of both agents, and every
    trade is two transfers, so the total of every good is unchanged. The agents trade until a round over all pairs of
    goods makes no trade, or until `limit` trades.

    Args:
        first:
            The first agent, with a state for the holding and for the weight of every good.

        second:
            The second agent.

        goods:
            The names of the states that hold the goods.

        weights:
            The names of the states that hold the weights of the goods, in the order of `goods`.

        quantity:
            The quantity of the bought good in every trade.

        limit:
            The largest number of trades.

    Returns:
        trades:
            The index of the bought good, the index of the good paid with, and the price of every trade.

    Examples:
        >>> from types import SimpleNamespace
        >>> from skcomplex.systems.economics.markets import trade_goods
        >>> first = SimpleNamespace(x=9.0, y=1.0, a=0.5, b=0.5)
        >>> second = SimpleNamespace(x=1.0, y=9.0, a=0.5, b=0.5)
        >>> trades = trade_goods(first, second, ('x', 'y'), ('a', 'b'), quantity=0.5)
        >>> first.x + second.x, len(trades) > 0
        (10.0, True)
    """
    trades: list[tuple[int, int, float]] = []
    pairs = list(itertools.combinations(range(len(goods)), 2))
    while len(trades) < limit:
        traded = False
        for pair in pairs:
            price = _trade_once(first, second, pair, goods=goods, weights=weights, quantity=quantity)
            if price is not None:
                trades.append((pair[0], pair[1], price))
                traded = True
        if not traded:
            break
    return trades