# pylint: disable=W0232, C1001, C0111, R0913, E1101, W0212
# TODO end_contract; record all payments
from __future__ import print_function
from builtins import str
from builtins import object
from abce.notenoughgoods import NotEnoughGoods
from random import shuffle
from abce.trade import get_epsilon
from .contracts import Contracts
epsilon = get_epsilon()
[docs]class Contract(object):
__slots__ = ['sender_group', 'sender_id', 'deliver_good_group',
'deliver_good_id', 'pay_group', 'pay_id', 'good', 'quantity',
'price', 'end_date', 'delivered', 'paid', 'id', 'round']
def __init__(self, sender_group, sender_id, deliver_good_group,
deliver_good_id, pay_group, pay_id, good, quantity, price,
end_date, id, round):
self.sender_group = sender_group
self.sender_id = sender_id
self.deliver_good_group = deliver_good_group
self.deliver_good_id = deliver_good_id
self.pay_group = pay_group
self.pay_id = pay_id
self.good = good
self.quantity = quantity
self.price = price
self.end_date = end_date
self.delivered = []
self.paid = []
self.id = id
self.round = round
def __str__(self):
return str(('sender', self.sender_group, self.sender_id, 'deliver', self.deliver_good_group,
self.deliver_good_id, self.pay_group, self.pay_id, self.good, self.quantity, self.price,
self.end_date, self.id, self.delivered, self.paid))
[docs]class Contracting(object):
""" This is a class, that allows you to create contracts. For example a
work contract. One agent commits to deliver a good or service for a set
amount of time.
For example you have a firm and a worker class. 'Labor' is set as a service
meaning that it lasts not longer than one round and the worker how has an
adult gets one unit of labor every round see: :meth:`abce.declare_service`.
The firm offers a work contract, the worker responds. Every round the
worker delivers the labor and the firm pays.::
class Firm(abce.Agent, abce.Contract)
def request_offer(self):
if self.round % 10 == 0:
self.given_contract = self.request_contract('contractbuyer', 0,
good='labor',
quantity=5,
price=10,
duration=10 - 1)
def deliver_or_pay(self):
self.pay_contract('labor')
class Worker(abce.Agent, abce.Contract):
def init(self):
self.create('adult', 1)
def accept_offer(self):
contracts = self.get_contract_requests('labor')
for contract in contracts:
if contract.price < 5:
self.accepted_contract = self.accept_contract(contract)
def deliver_or_pay(self):
self.deliver('labor')
Firms and workers can check, whether they have been paid/provided with
labor using the :meth:`is_paid` and :meth:`is_delivered` methods.
The worker can also initiate the transaction by requesting a contract with
:meth:`make_contract_offer`.
A contract has the following fields:
sender_group:
sender_id:
deliver_group:
deliver_id:
pay_group:
pay_id:
good:
quantity:
price:
end_date:
makerequest:
'm' for make_contract_offer and 'r' for request_contract
id:
unique number of contract
"""
def _add_contracts_list(self):
self.contracts = Contracts(self.name)
[docs] def offer_good_contract(self, receiver_group, receiver_id, good, quantity, price, duration):
"""This method offers a contract to provide a good or service to the
receiver. For a given time at a given price.
Args:
receiver_group:
group to receive the good
receiver_id:
group to receive the good
good:
the good or service that should be provided
quantity:
the quantity that should be provided
price:
the price of the good or service
duration:
the length of the contract, if duration is None or not set,
the contract has no end date.
Example::
self.given_contract = self.make_contract_offer('firm', 1, 'labor', quantity=8, price=10, duration=10 - 1)
"""
quantity = bound_zero(quantity)
if duration is None:
end_date = None
else:
end_date = duration + self.round
offer = Contract(sender_group=self.group,
sender_id=self.id,
deliver_good_group=self.group,
deliver_good_id=self.id,
pay_group=receiver_group,
pay_id=receiver_id,
good=good,
quantity=quantity,
price=price,
end_date=end_date,
id=self._offer_counter(),
round=self.round)
self._send(receiver_group, receiver_id, '!o', offer)
self._contract_offers_made[offer.id] = offer
return offer
[docs] def request_good_contract(self, receiver_group, receiver_id, good, quantity, price, duration):
"""This method requests a contract to provide a good or service to the
sender. For a given time at a given price. For example a job
advertisement.
Args:
receiver_group:
group of the receiver
receiver_id:
id of the receiver
good:
the good or service that should be provided
quantity:
the quantity that should be provided
price:
the price of the good or service
duration:
the length of the contract, if duration is None or not set,
the contract has no end date.
"""
quantity = bound_zero(quantity)
if duration is None:
end_date = None
else:
end_date = duration + self.round
offer = Contract(sender_group=self.group,
sender_id=self.id,
pay_group=self.group,
pay_id=self.id,
deliver_good_group=receiver_group,
deliver_good_id=receiver_id,
good=good,
quantity=quantity,
price=price,
end_date=end_date,
id=self._offer_counter(),
round=self.round)
self._send(receiver_group, receiver_id, '!o', offer)
self._contract_offers_made[offer.id] = offer
return offer
[docs] def get_contract_offers(self, good, descending=False):
""" Returns all contract offers and removes them. The contract
are ordered by price (ascending), when tied they are randomized.
Args:
good:
good that underlies the contract
descending(bool,default=False):
False for descending True for ascending by price
Returns:
list of contract offers ordered by price
"""
ret = self._contract_offers[good]
del self._contract_offers[good]
shuffle(ret)
ret.sort(key=lambda objects: objects.price, reverse=descending)
return ret
[docs] def accept_contract(self, contract, quantity=None):
""" Accepts the contract. The contract is completely accepted, when
the quantity is not given. Or partially when quantity is set.
Args:
contract:
the contract in question, received with get_contract_requests or
get_contract_offers
quantity (optional):
the quantity that is accepted. Defaults to all.
"""
if quantity is None:
quantity = contract.quantity
else:
contract.quantity = min(contract.quantity, quantity)
assert quantity < contract.quantity + \
epsilon * max(quantity, contract.quantity)
if quantity > contract.quantity:
quantity = contract.quantity
if contract.pay_group == self.group and contract.pay_id == self.id:
self.contracts._contracts_pay[contract.good][contract.id] = contract
self._send(contract.sender_group,
contract.sender_id, '_ac', contract)
else:
self.contracts._contracts_deliver[contract.good][contract.id] = contract
self._send(contract.sender_group,
contract.sender_id, '_ac', contract)
return contract
[docs] def deliver_contract(self, contract):
""" delivers on a contract """
assert contract.deliver_good_group == self.group and contract.deliver_good_id == self.id
quantity = contract.quantity
available = self._haves[contract.good]
if quantity > available + epsilon + epsilon * max(quantity, available):
raise NotEnoughGoods(self.name, contract.good,
quantity - available)
if quantity > available:
quantity = available
self._haves[contract.good] -= quantity
self._send(contract.pay_group, contract.pay_id, '_dp', contract)
[docs] def pay_contract(self, contract):
""" delivers on a contract """
assert contract.pay_group == self.group and contract.pay_id == self.id
money = contract.quantity * contract.price
available = self._haves['money']
if money > available + epsilon + epsilon * max(money, available):
raise NotEnoughGoods(self.name, 'money', money - available)
if money > available:
money = available
self._haves['money'] -= money
self._send(contract.deliver_good_group,
contract.deliver_good_id, '_dp', contract)
[docs] def contracts_to_deliver(self, good):
return list(self.contracts._contracts_deliver[good].values())
[docs] def contracts_to_receive(self, good):
return list(self.contracts._contracts_pay[good].values())
[docs] def contracts_to_deliver_all(self):
ret = {}
for good in self.contracts._contracts_deliver:
ret[good] = list(self.contracts._contracts_deliver[good].values())
return ret
[docs] def contracts_to_receive_all(self):
ret = {}
for good in self.contracts._contracts_pay:
ret[good] = list(self.contracts._contracts_pay[good].values())
return ret
[docs] def end_contract(self, contract):
if contract.id in self.contracts._contracts_deliver[contract.good]:
self._send(contract.pay_group, contract.pay_id,
'!d', ('r', contract.good, contract.id))
del self.contracts._contracts_deliver[contract.good][contract.id]
elif contract.id in self.contracts._contracts_pay[contract.good]:
self._send(contract.deliver_good_group, contract.deliver_good_id,
'!d', ('d', contract.good, contract.id))
del self.contracts._contracts_pay[contract.good][contract.id]
else:
raise Exception("Contract not found")
[docs] def was_paid_this_round(self, contract):
return contract.paid[-1] == self.round
[docs] def was_delivered_this_round(self, contract):
return contract.delivered[-1] == self.round
[docs] def was_paid_last_round(self, contract):
return self.round - 1 in contract.paid
[docs] def was_delivered_last_round(self, contract):
return self.round - 1 in contract.delivered
[docs] def calculate_netvalue(self, prices={},
parameters={},
value_functions={}):
return (self._haves.calculate_netvalue(prices) +
self.contracts.calculate_netvalue(parameters, value_functions))
[docs] def calculate_assetvalue(self, prices={},
parameters={},
value_functions={}):
return (self._haves.calculate_assetvalue(prices) +
self.contracts.calculate_assetvalue(parameters,
value_functions))
[docs] def calculate_liablityvalue(self, prices={},
parameters={},
value_functions={}):
return (self._haves.calculate_liablityvalue(prices) +
self.contracts.calculate_liablityvalue(parameters,
value_functions))
[docs] def calculate_valued_assets(self, prices={},
parameters={},
value_functions={}):
return (self._haves.calculate_valued_assets(prices) +
self.contracts.calculate_valued_assets(parameters,
value_functions))
[docs] def calculate_valued_liablities(self, prices={},
parameters={},
value_functions={}):
return (self._haves.calculate_valued_liablities(prices) +
self.contracts.calculate_valued_liablities(parameters,
value_functions))
[docs]def bound_zero(x):
""" asserts that variable is above zero, where foating point imprecission is accounted for,
and than makes sure it is above 0, without floating point imprecission """
assert x > - epsilon, \
'%.30f is smaller than 0 - epsilon (%.30f)' % (x, - epsilon)
if x < 0:
return 0
else:
return x