Source code for scml.scml2020.components.trading

import numpy as np
from pprint import pformat
from typing import List, Optional

from negmas import Contract

from scml.scml2020.components import FixedTradePredictionStrategy, SignAllPossible
from scml.scml2020.common import is_system_agent
from scml.scml2020.common import ANY_LINE
from scml.scml2020.components.prediction import MeanERPStrategy

__all__ = ["TradingStrategy", "ReactiveTradingStrategy", "PredictionBasedTradingStrategy"]


[docs]class TradingStrategy: """Base class for all trading strategies. Provides: - `inputs_needed` (np.ndarray): How many items of the input product do I need at every time step (n_steps vector) - `outputs_needed` (np.ndarray): How many items of the output product do I need at every time step (n_steps vector) - `inputs_secured` (np.ndarray): How many items of the output product do I need at every time step (n_steps vector) - `outputs_secured` (np.ndarray): How many units of the output product I have already secured per step (n_steps vector) Hooks Into: - `init` - `internal_state` Remarks: - `Attributes` section describes the attributes that can be used to construct the component (passed to its `__init__` method). - `Provides` section describes the attributes (methods, properties, data-members) made available by this component directly. Note that everything provided by the bases of this components are also available to the agent (Check the `Bases` section above for all the bases of this component). - `Requires` section describes any requirements from the agent using this component. It defines a set of methods or properties/data-members that must exist in the agent that uses this component. These requirement are usually implemented as abstract methods in the component - `Abstract` section describes abstract methods that MUST be implemented by any descendant of this component. - `Hooks Into` section describes the methods this component overrides calling `super` () which allows other components to hook into the same method (by overriding it). Usually callbacks starting with `on_` are hooked into this way. - `Overrides` section describes the methods this component overrides without calling `super` effectively disallowing any other components after it in the MRO to call this method. Usually methods that do some action (i.e. not starting with `on_`) are overridden this way. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.inputs_needed: np.ndarray = None """How many items of the input product do I need at every time step""" self.outputs_needed: np.ndarray = None """How many items of the output product do I need at every time step""" self.inputs_secured: np.ndarray = None """How many units of the input product I have already secured per step""" self.outputs_secured: np.ndarray = None """How many units of the output product I have already secured per step"""
[docs] def init(self): super().init() awi = self.awi # initialize needed/secured for inputs and outputs to all zeros self.inputs_secured = np.zeros(awi.n_steps, dtype=int) self.outputs_secured = np.zeros(awi.n_steps, dtype=int) self.inputs_needed = np.zeros(awi.n_steps, dtype=int) self.outputs_needed = np.zeros(awi.n_steps, dtype=int)
@property def internal_state(self): state = super().internal_state state.update({ "inputs_secured": self.inputs_secured if self.inputs_secured is not None else None, "inputs_needed": self.inputs_needed if self.inputs_needed is not None else None, "outputs_secured": self.outputs_secured if self.outputs_secured is not None else None, "outputs_needed": self.outputs_needed if self.outputs_needed is not None else None, "buy_negotiations": [ _.annotation["seller"] for _ in self.running_negotiations if _.annotation["buyer"] == self.id ], "sell_negotiations": [ _.annotation["buyer"] for _ in self.running_negotiations if _.annotation["seller"] == self.id ], "_balance": self.awi.state.balance, "_input_inventory": self.awi.state.inventory[self.awi.my_input_product], "_output_inventory": self.awi.state.inventory[self.awi.my_output_product], }) return state
class NoTradingStrategy(SignAllPossible, TradingStrategy): """A null trading strategy that just uses a signing strategy but no predictions. Remarks: - `Attributes` section describes the attributes that can be used to construct the component (passed to its `__init__` method). - `Provides` section describes the attributes (methods, properties, data-members) made available by this component directly. Note that everything provided by the bases of this components are also available to the agent (Check the `Bases` section above for all the bases of this component). - `Requires` section describes any requirements from the agent using this component. It defines a set of methods or properties/data-members that must exist in the agent that uses this component. These requirement are usually implemented as abstract methods in the component - `Abstract` section describes abstract methods that MUST be implemented by any descendant of this component. - `Hooks Into` section describes the methods this component overrides calling `super` () which allows other components to hook into the same method (by overriding it). Usually callbacks starting with `on_` are hooked into this way. - `Overrides` section describes the methods this component overrides without calling `super` effectively disallowing any other components after it in the MRO to call this method. Usually methods that do some action (i.e. not starting with `on_`) are overridden this way. """
[docs]class ReactiveTradingStrategy(SignAllPossible, TradingStrategy): """The agent reactively responds to contracts for selling by buying and vice versa. Hooks Into: - `on_contracts_finalized` Remarks: - `Attributes` section describes the attributes that can be used to construct the component (passed to its `__init__` method). - `Provides` section describes the attributes (methods, properties, data-members) made available by this component directly. Note that everything provided by the bases of this components are also available to the agent (Check the `Bases` section above for all the bases of this component). - `Requires` section describes any requirements from the agent using this component. It defines a set of methods or properties/data-members that must exist in the agent that uses this component. These requirement are usually implemented as abstract methods in the component - `Abstract` section describes abstract methods that MUST be implemented by any descendant of this component. - `Hooks Into` section describes the methods this component overrides calling `super` () which allows other components to hook into the same method (by overriding it). Usually callbacks starting with `on_` are hooked into this way. - `Overrides` section describes the methods this component overrides without calling `super` effectively disallowing any other components after it in the MRO to call this method. Usually methods that do some action (i.e. not starting with `on_`) are overridden this way. """
[docs] def on_contracts_finalized( self, signed: List[Contract], cancelled: List[Contract], rejectors: List[List[str]], ) -> None: # call the production strategy super().on_contracts_finalized(signed, cancelled, rejectors) this_step = self.awi.current_step inp, outp = self.awi.my_input_product, self.awi.my_output_product for contract in signed: t, q = contract.agreement["time"], contract.agreement["quantity"] if contract.annotation["caller"] == self.id: continue if contract.annotation["product"] != outp: continue is_seller = contract.annotation["seller"] == self.id # find the earliest time I can do anything about this contract if t > self.awi.n_steps - 1 or t < this_step: continue if is_seller: # if I am a seller, I will schedule production then buy my needs to produce steps, _ = self.awi.available_for_production(repeats=q, step=(this_step+1, t-1)) if len(steps) < 1: continue self.inputs_needed[min(steps)] += q continue # I am a buyer. I need not produce anything but I need to negotiate to sell the production of what I bought if inp != contract.annotation["product"]: continue self.outputs_needed[t] += q
[docs]class PredictionBasedTradingStrategy( FixedTradePredictionStrategy, MeanERPStrategy, TradingStrategy ): """A trading strategy that uses prediction strategies to manage inputs/outputs needed Hooks Into: - `init` - `on_contracts_finalized` - `sign_all_contracts` - `on_agent_bankrupt` Remarks: - `Attributes` section describes the attributes that can be used to construct the component (passed to its `__init__` method). - `Provides` section describes the attributes (methods, properties, data-members) made available by this component directly. Note that everything provided by the bases of this components are also available to the agent (Check the `Bases` section above for all the bases of this component). - `Requires` section describes any requirements from the agent using this component. It defines a set of methods or properties/data-members that must exist in the agent that uses this component. These requirement are usually implemented as abstract methods in the component - `Abstract` section describes abstract methods that MUST be implemented by any descendant of this component. - `Hooks Into` section describes the methods this component overrides calling `super` () which allows other components to hook into the same method (by overriding it). Usually callbacks starting with `on_` are hooked into this way. - `Overrides` section describes the methods this component overrides without calling `super` effectively disallowing any other components after it in the MRO to call this method. Usually methods that do some action (i.e. not starting with `on_`) are overridden this way. """
[docs] def init(self): super().init() # If I expect to sell x outputs at step t, I should buy x inputs at t-1 self.inputs_needed[:-1] = self.expected_outputs[1:] # If I expect to buy x inputs at step t, I should sell x inputs at t+1 self.outputs_needed[1:] = self.expected_inputs[:-1]
[docs] def on_contracts_finalized( self, signed: List[Contract], cancelled: List[Contract], rejectors: List[List[str]], ) -> None: self.awi.logdebug_agent( f"Enter Contracts Finalized:\n" f"Signed {pformat([self._format(_) for _ in signed])}\n" f"Cancelled {pformat([self._format(_) for _ in cancelled])}\n" f"{pformat(self.internal_state)}" ) super().on_contracts_finalized(signed, cancelled, rejectors) consumed = 0 for contract in signed: is_seller = contract.annotation["seller"] == self.id q, u, t = ( contract.agreement["quantity"], contract.agreement["unit_price"], contract.agreement["time"], ) if is_seller: # if I am a seller, I will buy my needs to produce output_product = contract.annotation["product"] input_product = output_product - 1 self.outputs_secured[t] += q if input_product >= 0 and t > 0: # find the maximum possible production I can do and saturate to it steps, lines = self.awi.available_for_production( repeats=q, step=(self.awi.current_step, t - 1) ) q = min(len(steps) - consumed, q) consumed += q if contract.annotation["caller"] != self.id: # this is a sell contract that I did not expect yet. Update needs accordingly self.inputs_needed[t - 1] += max(1, q) continue # I am a buyer. I need not produce anything but I need to negotiate to sell the production of what I bought input_product = contract.annotation["product"] output_product = input_product + 1 self.inputs_secured[t] += q if output_product < self.awi.n_products and t < self.awi.n_steps - 1: if contract.annotation["caller"] != self.id: # this is a buy contract that I did not expect yet. Update needs accordingly self.outputs_needed[t + 1] += max(1, q) self.awi.logdebug_agent( f"Exit Contracts Finalized:\n{pformat(self.internal_state)}" )
[docs] def sign_all_contracts(self, contracts: List[Contract]) -> List[Optional[str]]: # sort contracts by time and then put system contracts first within each time-step signatures = [None] * len(contracts) contracts = sorted( zip(contracts, range(len(contracts))), key=lambda x: ( x[0].agreement["unit_price"], x[0].agreement["time"], 0 if is_system_agent(x[0].annotation["seller"]) or is_system_agent(x[0].annotation["buyer"]) else 1, x[0].agreement["unit_price"], ), ) taken = 0 s = self.awi.current_step for contract, indx in contracts: q, u, t = ( contract.agreement["quantity"], contract.agreement["unit_price"], contract.agreement["time"], ) # check that the contract is executable in principle if t <= s and len(contract.issues) == 3: continue if contract.annotation["seller"] == self.id: trange = (s, t) secured, needed = (self.outputs_secured, self.outputs_needed) else: trange = (t + 1, self.awi.n_steps - 1) secured, needed = (self.inputs_secured, self.inputs_needed) # check that I can produce the required quantities even in principle steps, lines = self.awi.available_for_production( q, trange, ANY_LINE, override=False, method="all" ) if len(steps) - taken < q: continue if ( secured[trange[0] : trange[1] + 1].sum() + q + taken <= needed[trange[0] : trange[1] + 1].sum() ): signatures[indx] = self.id taken += self.predict_quantity(contract) return signatures
def _format(self, c: Contract): return ( f"{f'>' if c.annotation['seller'] == self.id else '<'}" f"{c.annotation['buyer'] if c.annotation['seller'] == self.id else c.annotation['seller']}: " f"{c.agreement['quantity']} of {c.annotation['product']} @ {c.agreement['unit_price']} on {c.agreement['time']}" )
[docs] def on_agent_bankrupt( self, agent: str, contracts: List[Contract], quantities: List[int], compensation_money: int, ) -> None: super().on_agent_bankrupt(agent, contracts, quantities, compensation_money) for contract, new_quantity in zip(contracts, quantities): q = contract.agreement["quantity"] if new_quantity == q: continue t = contract.agreement["time"] missing = q - new_quantity if t < self.awi.current_step: continue if contract.annotation["seller"] == self.id: self.outputs_secured[t] -= missing if t > 0: self.inputs_needed[t - 1] -= missing else: self.inputs_secured[t] += missing if t < self.awi.n_steps - 1: self.outputs_needed[t + 1] -= missing