import numpy as np
import pandas as pd
from pymarket.bids import BidManager
[docs]def demand_curve_from_bids(bids):
"""
Creates a demand curve from a set of buying bids.
It is the inverse cumulative distribution of quantity
as a function of price.
Parameters
----------
bids
Collection of all the bids in the market. The algorithm
filters only the buying bids.
Returns
---------
demand_curve: np.ndarray
Stepwise constant demand curve represented as a collection
of the N rightmost points of each interval (N-1 bids). It is stored
as a (N, 2) matrix where the first column is the x-coordinate
and the second column is the y-coordinate.
An extra point is a))dded with x coordinate at infinity and
price at 0 to represent the end of the curve.
index : np.ndarray
The order of the identifier of each bid in the demand
curve.
Examples
---------
A minimal example, selling bid is ignored:
>>> bm = pm.BidManager()
>>> bm.add_bid(1, 1, 0, buying=True)
0
>>> bm.add_bid(1, 1, 1, buying=False)
1
>>> dc, index = pm.demand_curve_from_bids(bm.get_df())
>>> dc
array([[ 1., 1.],
[inf, 0.]])
>>> index
array([0])
A larger example with reordering of bids:
>>> bm = pm.BidManager()
>>> bm.add_bid(1, 1, 0, buying=True)
0
>>> bm.add_bid(1, 1, 1, buying=False)
1
>>> bm.add_bid(3, 0.5, 2, buying=True)
2
>>> bm.add_bid(2.3, 0.1, 3, buying=True)
3
>>> dc, index = pm.demand_curve_from_bids(bm.get_df())
>>> dc
array([[1. , 1. ],
[4. , 0.5],
[6.3, 0.1],
[inf, 0. ]])
>>> index
array([0, 2, 3])
"""
buying = bids[bids.buying]
buying = buying.sort_values('price', ascending=False)
buying['acum'] = buying.quantity.cumsum()
demand_curve = buying[['acum', 'price']].values
demand_curve = np.vstack([demand_curve, [np.inf, 0]])
index = buying.index.values.astype('int64')
return demand_curve, index
[docs]def supply_curve_from_bids(bids):
"""
Creates a supply curve from a set of selling bids.
It is the cumulative distribution of quantity
as a function of price.
Parameters
----------
bids: pd.DataFrame
Collection of all the bids in the market. The algorithm
filters only the selling bids.
Returns
---------
supply_curve: np.ndarray
Stepwise constant demand curve represented as a collection
of the N rightmost points of each interval (N-1 bids). It is stored
as a (N, 2) matrix where the first column is the x-coordinate
and the second column is the y-coordinate.
An extra point is added with x coordinate at infinity and
price at infinity to represent the end of the curve.
index : np.ndarray
The order of the identifier of each bid in the supply
curve.
Examples
---------
A minimal example, selling bid is ignored:
>>> bm = pm.BidManager()
>>> bm.add_bid(1, 3, 0, False)
0
>>> bm.add_bid(2.1, 3, 3, True)
1
>>> sc, index = pm.supply_curve_from_bids(bm.get_df())
>>> sc
array([[ 1., 3.],
[inf, inf]])
>>> index
array([0])
A larger example with reordering:
>>> bm = pm.BidManager()
>>> bm.add_bid(1, 3, 0, False)
0
>>> bm.add_bid(2.1, 3, 3, True)
1
>>> bm.add_bid(0.2, 1, 3, False)
2
>>> bm.add_bid(1.7, 6, 4, False)
3
>>> sc, index = pm.supply_curve_from_bids(bm.get_df())
>>> sc
array([[0.2, 1. ],
[1.2, 3. ],
[2.9, 6. ],
[inf, inf]])
>>> index
array([2, 0, 3])
"""
selling = bids[bids.buying == False]
selling = selling.sort_values('price')
selling['acum'] = selling.quantity.cumsum()
supply_curve = selling[['acum', 'price']].values
supply_curve = np.vstack([supply_curve, [np.inf, np.inf]])
index = selling.index.values.astype('int64')
return supply_curve, index
[docs]def get_value_stepwise(x, f):
"""
Returns the value of a stepwise constant
function defined by the right extrems
of its interval
Functions are assumed to be defined
in (0, inf).
Parameters
----------
x: float
Value in which the function is to be
evaluated
f: np.ndarray
Stepwise function represented as a 2 column
matrix. Each row is the rightmost extreme
point of each constant interval. The first column
contains the x coordinate and is sorted increasingly.
f is assumed to be defined only in the interval
:math: (0, \infty)
Returns
--------
float or None
The image of x under f: `f(x)`. If `x` is negative,
then None is returned instead. If x is outside
the range of the function (greater than `f[-1, 0]`),
then the method returns None.
Examples
---------
>>> f = np.array([
... [1, 1],
... [3, 4]])
>>> [pm.get_value_stepwise(x, f)
... for x in [-1, 0, 0.5, 1, 2, 3, 4]]
[None, 1, 1, 1, 4, 4, None]
"""
if x < 0:
return None
for step in f:
if x <= step[0]:
return step[1]
[docs]def intersect_stepwise(
f,
g,
k=0.5
):
"""
Finds the intersection of
two stepwise constants functions
where f is assumed to be bigger at 0
than g.
If no intersection is found, None is returned.
Parameters
----------
f: np.ndarray
Stepwise constant function represented as
a 2 column matrix where each row is the rightmost
point of the constat interval. The first column
is sorted increasingly.
Preconditions: f is non-increasing.
g: np.ndarray
Stepwise constant function represented as
a 2 column matrix where each row is the rightmost
point of the constat interval. The first column
is sorted increasingly.
Preconditions: g is non-decreasing and
`f[0, 0] > g[0, 0]`
k : float
If the intersection is empty or an interval,
a convex combination of the y-values of f and g
will be returned and k will be used to determine
hte final value. `k=1` will be the value of g
while `k=0` will be the value of f.
Returns
--------
x_ast : float or None
Axis coordinate of the intersection of both
functions. If the intersection is empty,
then it returns None.
f_ast : int or None
Index of the rightmost extreme
of the interval of `f` involved in the
intersection. If the intersection is
empty, returns None
g_ast : int or None
Index of the rightmost extreme
of the interval of `g` involved in the
intersection. If the intersection is
empty, returns None.
v : float or None
Ordinate of the intersection if it
is uniquely identified, otherwise
the k-convex combination of the
y values of `f` and `g` in the last
point when they were both defined.
Examples
---------
Simple intersection with diferent domains
>>> f = np.array([[1, 3], [3, 1]])
>>> g = np.array([[2,2]])
>>> pm.intersect_stepwise(f, g)
(1, 0, 0, 2)
Empty intersection, returning the middle value
>>> f = np.array([[1,3], [2, 2.5]])
>>> g = np.array([[1,1], [2, 2]])
>>> pm.intersect_stepwise(f, g)
(None, None, None, 2.25)
"""
x_max = np.min([f.max(axis=0)[0], g.max(axis=0)[0]])
xs = sorted([x for x in set(g[:, 0]).union(set(f[:, 0])) if x <= x_max])
fext = [get_value_stepwise(x, f) for x in xs]
gext = [get_value_stepwise(x, g) for x in xs]
x_ast = None
for i in range(len(xs) - 1):
if (fext[i] > gext[i]) and (fext[i + 1] < gext[i + 1]):
x_ast = xs[i]
f_ast = np.argmax(f[:, 0] >= x_ast) if x_ast is not None else None
g_ast = np.argmax(g[:, 0] >= x_ast) if x_ast is not None else None
g_val = g[g_ast, 1] if g_ast is not None else get_value_stepwise(xs[-1], g)
f_val = f[f_ast, 1] if f_ast is not None else get_value_stepwise(xs[-1], f)
intersect_domain_both = x_ast in f[:, 0] and x_ast in g[:, 0]
if not (intersect_domain_both) and (x_ast is not None):
v = g_val if x_ast in f[:, 0] else f_val
else:
v = g_val * k + (1 - k) * f_val
return x_ast, f_ast, g_ast, v