├── pyapp ├── src │ └── lem_sim │ │ ├── globalmemory │ │ ├── __init__.py │ │ └── variables.py │ │ ├── analyse │ │ ├── __init__.py │ │ ├── run.py │ │ └── event_handler.py │ │ ├── communication │ │ ├── __init__.py │ │ └── connect.py │ │ ├── client │ │ ├── __init__.py │ │ ├── agent.py │ │ └── dealer.py │ │ ├── __init__.py │ │ ├── utils │ │ ├── process_values.py │ │ ├── mining_observer.py │ │ ├── __init__.py │ │ ├── contract_communication.py │ │ └── order_handler.py │ │ ├── linearoptimization │ │ ├── __init__.py │ │ ├── optimization_problem.py │ │ └── decompose.py │ │ ├── contract │ │ ├── __init__.py │ │ └── contract_handler.py │ │ ├── test │ │ ├── test_dealer_events.py │ │ ├── test_dealer_function.py │ │ ├── test_decompose.py │ │ ├── test_order_handler.py │ │ ├── test_dealer_values.py │ │ └── test_linearoptimization.py │ │ ├── output │ │ ├── __init__.py │ │ ├── interface.py │ │ ├── draw.py │ │ └── log.py │ │ └── simulate.py ├── .flake8 ├── requirements.txt ├── setup.py └── Makefile ├── .img ├── sequenz.png └── platform_architecture.png ├── Dockerfile ├── docker-compose.yml ├── dealer ├── migrations │ └── 1_initial_migration.js ├── contracts │ ├── Migrations.sol │ └── Dealer.sol └── truffle-config.js ├── .gitignore └── README.md /pyapp/src/lem_sim/globalmemory/__init__.py: -------------------------------------------------------------------------------- 1 | from .variables import Variables # noqa -------------------------------------------------------------------------------- /.img/sequenz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nreinhol/lem_sim/HEAD/.img/sequenz.png -------------------------------------------------------------------------------- /pyapp/src/lem_sim/analyse/__init__.py: -------------------------------------------------------------------------------- 1 | from .event_handler import EventHandler # noqa -------------------------------------------------------------------------------- /pyapp/src/lem_sim/communication/__init__.py: -------------------------------------------------------------------------------- 1 | from .connect import get_network_connection # noqa -------------------------------------------------------------------------------- /pyapp/src/lem_sim/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .agent import Agent # noqa 2 | from .dealer import Dealer # noqa -------------------------------------------------------------------------------- /.img/platform_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nreinhol/lem_sim/HEAD/.img/platform_architecture.png -------------------------------------------------------------------------------- /pyapp/src/lem_sim/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path # noqa 2 | 3 | # Constants 4 | PROJECT_DIR = str(Path(__file__).resolve().parents[2]) 5 | -------------------------------------------------------------------------------- /pyapp/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = H101, H202, E501, C901 3 | exclude = 4 | .git, 5 | __pycache__, 6 | data/raw, 7 | env, 8 | max-complexity = 10 -------------------------------------------------------------------------------- /pyapp/requirements.txt: -------------------------------------------------------------------------------- 1 | # local package 2 | -e . 3 | 4 | # external requirements 5 | click==6.7 6 | scipy==1.2.1 7 | flake8==3.5.0 8 | web3==4.2.0 9 | numpy==1.16.2 10 | cvxopt -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nreinhol/python3_web3:1.0 2 | 3 | COPY ./pyapp ./app 4 | WORKDIR ./app 5 | 6 | RUN pip3 install -r requirements.txt 7 | 8 | CMD [ "python", "./src/lem_sim/simulate.py" ] 9 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/utils/process_values.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def truncate_values_of_array(array): 5 | Ndecimals = 2 6 | decade = 10**Ndecimals 7 | return np.trunc(array * decade) / decade 8 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/linearoptimization/__init__.py: -------------------------------------------------------------------------------- 1 | from .optimization_problem import OptimizationProblem # noqa 2 | from .decompose import decompose # noqa 3 | from .decompose import remove_zero_rows_of_individual_coefs # noqa -------------------------------------------------------------------------------- /pyapp/src/lem_sim/contract/__init__.py: -------------------------------------------------------------------------------- 1 | from .contract_handler import ContractHandler # noqa 2 | 3 | from pathlib import Path 4 | 5 | PROJECT_DIR = str(Path(__file__).resolve().parents[4]) 6 | CONTRACT_DIR = PROJECT_DIR + '/dealer/build/contracts/' 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | ganache: 5 | image: trufflesuite/ganache-cli:v6.4.1 6 | ports: 7 | - "8545:8545" 8 | command: ["-a 3", "-e 100"] 9 | agents: 10 | build: . 11 | depends_on: 12 | - ganache 13 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/utils/mining_observer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | def wait_for_new_block(var): 4 | while var.latest_block >= var.web3.eth.getBlock('latest')['number']: 5 | time.sleep(0.5) 6 | 7 | var.latest_block = var.web3.eth.getBlock('latest')['number'] 8 | -------------------------------------------------------------------------------- /pyapp/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name='lem_sim', 5 | packages=find_packages('src'), 6 | version='0.0.1', 7 | description='A blockchain-based LEM simulation', 8 | author='Niklas R.', 9 | package_dir={"": "src"}, 10 | ) 11 | -------------------------------------------------------------------------------- /dealer/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | const Dealer = artifacts.require("Dealer"); 3 | 4 | module.exports = function(deployer, network, accounts) { 5 | deployer.deploy(Migrations) 6 | deployer.deploy(Dealer, {from: accounts[0]}) 7 | }; 8 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_dealer_events.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lem_sim import globalmemory as mem 4 | 5 | 6 | class DealerEventsTest(unittest.TestCase): 7 | 8 | def test_received_order(self): 9 | 10 | var = mem.Variables() 11 | 12 | myfilter = var.dealer._dealer_contract.contract.events.ReceivedOrder.createFilter(fromBlock=0, toBlock='latest') 13 | eventlist = myfilter.get_all_entries() 14 | for entry in eventlist: 15 | pass 16 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/output/__init__.py: -------------------------------------------------------------------------------- 1 | from .draw import print_iteration_stats # noqa 2 | from .draw import print_initial_setup # noqa 3 | from .draw import print_verify_strong_duality_failed # noqa 4 | from .log import log_iteration_stats # noqa 5 | from .log import log_initial_setup # noqa 6 | from .log import log_verify_strong_duality_failed # noqa 7 | from .interface import initial_setup # noqa 8 | from .interface import iteration_stats # noqa 9 | from .interface import verify_strong_duality_failed # noqa -------------------------------------------------------------------------------- /pyapp/src/lem_sim/communication/connect.py: -------------------------------------------------------------------------------- 1 | import time 2 | import click 3 | from web3 import Web3, HTTPProvider 4 | 5 | 6 | def get_network_connection(connection='ip'): 7 | 8 | if(connection == 'docker'): 9 | network_address = 'HTTP://ganache:8545' 10 | time.sleep(10) 11 | elif(connection == 'ip'): 12 | network_address = 'HTTP://0.0.0.0:8545' 13 | else: 14 | click.secho('No connection address specified!', fg='red') 15 | quit() 16 | 17 | return Web3(HTTPProvider(network_address)) 18 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/output/interface.py: -------------------------------------------------------------------------------- 1 | from lem_sim import output 2 | 3 | 4 | def initial_setup(var, draw=True): 5 | if(draw): 6 | output.print_initial_setup(var) 7 | 8 | output.log_initial_setup(var) 9 | 10 | 11 | def iteration_stats(var, iteration, draw=True): 12 | if(draw): 13 | output.print_iteration_stats(var, iteration) 14 | 15 | output.log_iteration_stats(var, iteration) 16 | 17 | 18 | def verify_strong_duality_failed(agent, draw=True): 19 | if(draw): 20 | output.print_verify_strong_duality_failed(agent) 21 | 22 | output.log_verify_strong_duality_failed(agent) 23 | -------------------------------------------------------------------------------- /dealer/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .contract_communication import shift_decimal_right # noqa 2 | from .contract_communication import shift_decimal_left # noqa 3 | from .contract_communication import prepare_for_sending # noqa 4 | from .contract_communication import prepare_for_storing # noqa 5 | from .contract_communication import from_ether_to_wei # noqa 6 | from .contract_communication import from_wei_to_ether # noqa 7 | from .order_handler import Orders # noqa 8 | from .order_handler import OrderHandler # noqa 9 | from .process_values import truncate_values_of_array # noqa 10 | from .mining_observer import wait_for_new_block # noqa -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_dealer_function.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | from lem_sim import globalmemory as mem 5 | 6 | 7 | class DealerFunctionsTest(unittest.TestCase): 8 | 9 | def test_owner(self): 10 | var = mem.Variables() 11 | owner = var.dealer_contract.contract.functions.getOwner().call() 12 | self.assertEqual(owner, var.dealer.account_address) 13 | 14 | def test_mkt_prices(self): 15 | var = mem.Variables() 16 | agent = var.agent_pool[0] 17 | dealer = var.dealer 18 | 19 | # set mkt price vector 20 | dealer.mkt_prices = np.array([3, 4]) 21 | # set mkt prices in contract 22 | dealer.set_mkt_prices() 23 | # get mkt prices from contract 24 | agent.get_mkt_prices() 25 | 26 | self.assertTrue(np.array_equal(dealer.mkt_prices, agent.mkt_prices)) 27 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/output/draw.py: -------------------------------------------------------------------------------- 1 | import click as c 2 | 3 | 4 | def print_initial_setup(var): 5 | c.secho('\n__________________________________ ', fg='green', bold=True) 6 | c.secho('CENTRAL LP ', fg='green', bold=True) 7 | c.secho(var.central_problem.__str__()) 8 | c.secho('\n__________________________________ ', fg='green', bold=True) 9 | c.secho('INITIAL SETUP', fg='green', bold=True) 10 | c.secho(var.dealer.__str__()) 11 | 12 | for agent in var.agent_pool: 13 | c.secho(agent.__str__()) 14 | 15 | 16 | def print_iteration_stats(var, iteration): 17 | c.secho('\n__________________________________ ', fg='green', bold=True) 18 | c.secho('ITERATION {}'.format(iteration), fg='green', bold=True) 19 | c.secho(var.dealer.__str__()) 20 | 21 | for agent in var.agent_pool: 22 | c.secho(agent.__str__()) 23 | 24 | 25 | def print_verify_strong_duality_failed(agent): 26 | c.secho('\n{} did not accept the trades bill!'.format(agent.name), fg='red', bold=True) 27 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/output/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | 4 | from lem_sim import PROJECT_DIR 5 | 6 | start_time_string = datetime.now().strftime('%d.%m.%Y_%H:%M') 7 | logging.basicConfig(format='', filename='{}/logs/{}_simulation.log'.format(PROJECT_DIR, start_time_string), 8 | level=logging.INFO) 9 | 10 | 11 | def log_initial_setup(var): 12 | logging.info('__________________________________ ') 13 | logging.info('CENTRAL LP') 14 | logging.info(var.central_problem) 15 | logging.info('__________________________________ ') 16 | logging.info('INITIAL SETUP') 17 | logging.info(var.dealer) 18 | 19 | for agent in var.agent_pool: 20 | logging.info('\n{}'.format(agent)) 21 | 22 | 23 | def log_iteration_stats(var, iteration): 24 | logging.info('\n__________________________________') 25 | logging.info('ITERATION {}'.format(iteration)) 26 | logging.info(var.dealer) 27 | 28 | for agent in var.agent_pool: 29 | logging.info('\n{}'.format(agent)) 30 | 31 | 32 | def log_verify_strong_duality_failed(agent): 33 | logging.info('\n{} did not accept the trades bill!'.format(agent.name)) 34 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/utils/contract_communication.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from web3 import Web3 3 | 4 | DECIMAL_PLACES = 2 5 | MULTIPLIER = 10**DECIMAL_PLACES 6 | 7 | 8 | def from_ether_to_wei(value): 9 | return Web3.toWei(value, 'ether') 10 | 11 | 12 | def from_wei_to_ether(value): 13 | return Web3.fromWei(value, 'ether') 14 | 15 | 16 | def prepare_for_sending(value): 17 | if type(value) is np.ndarray: 18 | value = value.tolist() 19 | return shift_decimal_right(value) 20 | else: 21 | return shift_decimal_right(value) 22 | 23 | 24 | def prepare_for_storing(value): 25 | if type(value) is list: 26 | value = shift_decimal_left(value) 27 | return np.array(value) 28 | else: 29 | return shift_decimal_left(value) 30 | 31 | 32 | def shift_decimal_right(value): 33 | if type(value) is list: 34 | return [int(i * MULTIPLIER) for i in value] 35 | if type(value) is int or float: 36 | return int(value * MULTIPLIER) 37 | 38 | 39 | def shift_decimal_left(value): 40 | if type(value) is list: 41 | return [float(i / MULTIPLIER) for i in value] 42 | if type(value) is int or float: 43 | return float(value / MULTIPLIER) 44 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/contract/contract_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from lem_sim import contract 4 | 5 | 6 | class ContractHandler(object): 7 | 8 | def __init__(self, web3, artefact_name): 9 | self._web3 = web3 10 | self._contract_dir = contract.CONTRACT_DIR 11 | self._artefact_name = artefact_name 12 | self._contract_artefact = self.get_contract_artefact() 13 | self._contract_abi = self._contract_artefact['abi'] 14 | self._contract_address = self.get_address() 15 | self._contract = self._web3.eth.contract(address=self._contract_address, abi=self._contract_abi) 16 | 17 | @property 18 | def contract(self): 19 | return self._contract 20 | 21 | def get_contract_artefact(self): 22 | with open(self._contract_dir + self._artefact_name) as artefact_json: 23 | return json.load(artefact_json) 24 | 25 | def get_address(self): 26 | networks = self._contract_artefact['networks'] 27 | addresses = [] 28 | 29 | for network, attribute in networks.items(): 30 | addresses.append(attribute['address']) 31 | 32 | # artefact can have multiple networks, always returns the current/last network 33 | return addresses[-1] 34 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_decompose.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | 5 | class DecomposeTest(unittest.TestCase): 6 | 7 | def test_concatenate_two_rows_of_arrays(self): 8 | first_row = np.array([-1, -2]) 9 | second_row = np.array([0, 0]) 10 | result = np.concatenate((first_row, second_row), axis=None) 11 | expected = np.array([-1, -2, 0, 0]) 12 | 13 | self.assertTrue(np.array_equal(result, expected)) 14 | 15 | def test_add_identity_matrix(self): 16 | # create identity matrix with n = array size 17 | random_array = np.array([1, 2]) 18 | n = random_array.size 19 | identity_matrix = np.identity(n) 20 | 21 | # add identity matrix to a given array 22 | array = np.array([[1, 3], [1, 1]]) 23 | merged_array = np.concatenate((array, identity_matrix), axis=1) 24 | expected = np.array([[1, 3, 1, 0], [1, 1, 0, 1]]) 25 | 26 | self.assertTrue(np.array_equal(merged_array, expected)) 27 | 28 | def test_create_array_with_zeros(self): 29 | zeros = np.zeros(2) 30 | expected = np.array([0, 0]) 31 | 32 | self.assertTrue(np.array_equal(zeros, expected)) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_order_handler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | from lem_sim import globalmemory as mem 5 | from lem_sim import utils 6 | 7 | 8 | class OrdersTest(unittest.TestCase): 9 | 10 | def test_get_concatenated_bundles(self): 11 | orders = utils.Orders('test09') 12 | 13 | # 1st order 14 | bundle_i = np.array([8, 3]) 15 | bid_i = 6 16 | indice_i = 0 17 | # 2nd order 18 | bundle_j = np.array([2, 3]) 19 | bid_j = 7 20 | indice_j = 1 21 | # 3rd order 22 | bundle_k = np.array([6, 7]) 23 | bid_k = 7 24 | indice_k = 2 25 | 26 | orders.add_order(bundle_i, bid_i, indice_i) 27 | orders.add_order(bundle_j, bid_j, indice_j) 28 | orders.add_order(bundle_k, bid_k, indice_k) 29 | 30 | def test_calculate_trade(self): 31 | first_order = ['account_1', [8, 3], 600] 32 | second_order = ['account_1', [2, 9], 300] 33 | 34 | var = mem.Variables() 35 | dealer = var.dealer 36 | dealer._mmp_values = np.array([1, 1]) 37 | dealer._order_handler = utils.OrderHandler() 38 | dealer._order_handler.add_order(0, first_order) 39 | dealer._order_handler.add_order(1, second_order) 40 | dealer.set_trade_share() 41 | orders = dealer._order_handler.get_all_orders() 42 | orders[0].calculate_prepayment() 43 | 44 | self.assertEqual(orders[0]._prepayment, 9) 45 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/analyse/run.py: -------------------------------------------------------------------------------- 1 | import click as c 2 | 3 | from lem_sim import analyse 4 | 5 | 6 | @c.command() 7 | @c.option('--option', type=c.Choice(['events'])) 8 | def run(option): 9 | 10 | if(option == 'events'): 11 | event_handler = analyse.EventHandler() 12 | received_order_events = event_handler.filter_received_order_event(0, 'latest') 13 | stored_trade_events = event_handler.filter_stored_trade_event(0, 'latest') 14 | fetched_trade_events = event_handler.filter_fetched_trade_event(0, 'latest') 15 | rejected_trade_events = event_handler.filter_rejected_trade_event(0, 'latest') 16 | deleted_order_events = event_handler.filter_deleted_order_event(0, 'latest') 17 | 18 | print('\nRECEIVED ORDERS') 19 | for event in received_order_events: 20 | print(event['args'], event['blockNumber']) 21 | 22 | print('\nSTORED TRADES') 23 | for event in stored_trade_events: 24 | print(event['args'], event['blockNumber']) 25 | 26 | print('\nFETCHED TRADES') 27 | for event in fetched_trade_events: 28 | print(event['args'], event['blockNumber']) 29 | 30 | print('\nREJECTED TRADES') 31 | for event in rejected_trade_events: 32 | print(event['args'], event['blockNumber']) 33 | 34 | print('\nDELETED ORDERS / SETTLED ORDERS') 35 | for event in deleted_order_events: 36 | print(event['args'], event['blockNumber']) 37 | 38 | 39 | if __name__ == '__main__': 40 | run() 41 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/analyse/event_handler.py: -------------------------------------------------------------------------------- 1 | from lem_sim import communication 2 | from lem_sim import contract 3 | 4 | 5 | class EventHandler(object): 6 | 7 | def __init__(self): 8 | self._web3 = communication.get_network_connection() 9 | self._dealer_contract = contract.ContractHandler(self._web3, 'Dealer.json') 10 | 11 | def filter_received_order_event(self, from_block, to_block): 12 | event_filter = self._dealer_contract.contract.events.ReceivedOrder.createFilter(fromBlock=from_block, toBlock=to_block) 13 | return event_filter.get_all_entries() 14 | 15 | def filter_stored_trade_event(self, from_block, to_block): 16 | event_filter = self._dealer_contract.contract.events.StoredTrade.createFilter(fromBlock=from_block, toBlock=to_block) 17 | return event_filter.get_all_entries() 18 | 19 | def filter_fetched_trade_event(self, from_block, to_block): 20 | event_filter = self._dealer_contract.contract.events.FetchedTrade.createFilter(fromBlock=from_block, toBlock=to_block) 21 | return event_filter.get_all_entries() 22 | 23 | def filter_rejected_trade_event(self, from_block, to_block): 24 | event_filter = self._dealer_contract.contract.events.RejectedTrade.createFilter(fromBlock=from_block, toBlock=to_block) 25 | return event_filter.get_all_entries() 26 | 27 | def filter_deleted_order_event(self, from_block, to_block): 28 | event_filter = self._dealer_contract.contract.events.DeletedOrder.createFilter(fromBlock=from_block, toBlock=to_block) 29 | return event_filter.get_all_entries() 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # ide stuff 107 | .vscode 108 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/linearoptimization/optimization_problem.py: -------------------------------------------------------------------------------- 1 | from scipy.optimize import linprog 2 | import numpy as np 3 | 4 | 5 | class OptimizationProblem(object): 6 | 7 | def __init__(self, target_coefs, individual_resources, individual_coefs, shared_resources, shared_coefs): 8 | self._target_coefs = target_coefs # cost vectors 9 | self._individual_resources = individual_resources # individual resources 10 | self._shared_resources = shared_resources # shared resources 11 | self._individual_coefs = individual_coefs # individual coefficients 12 | self._shared_coefs = shared_coefs # shared coefficients 13 | 14 | @property 15 | def target_coefs(self): 16 | return self._target_coefs 17 | 18 | @property 19 | def individual_resources(self): 20 | return self._individual_resources 21 | 22 | @property 23 | def individual_coefs(self): 24 | return self._individual_coefs 25 | 26 | @property 27 | def shared_resources(self): 28 | return self._shared_resources 29 | 30 | @shared_resources.setter 31 | def shared_resources(self, value): 32 | self._shared_resources = value 33 | 34 | @property 35 | def shared_coefs(self): 36 | return self._shared_coefs 37 | 38 | def solve(self): 39 | constraint_coefs = np.concatenate((self._individual_coefs, self._shared_coefs)) 40 | constraint_resources = np.concatenate((self._individual_resources, self._shared_resources)) 41 | 42 | return linprog(self._target_coefs, constraint_coefs, constraint_resources) 43 | 44 | def __str__(self): 45 | class_str = 'Target Coefficients:\n{}\nIndividual Coefficients:\n{}\nIndividual Resources:\n{}\nShared Coefficients:\n{}\nShared Resources:\n{}'.format( 46 | self._target_coefs, 47 | self._individual_coefs, 48 | self._individual_resources, 49 | self._shared_coefs, 50 | self._shared_resources 51 | ) 52 | 53 | return class_str 54 | -------------------------------------------------------------------------------- /pyapp/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean data jupyter lint requirements venv 2 | 3 | ################################################################################# 4 | # GLOBALS # 5 | ################################################################################# 6 | PROJECT_NAME = lem_sim 7 | PROJECT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 8 | VENV_DIR = $(PROJECT_DIR)/env 9 | 10 | PYTHON_INTERPRETER = $(VENV_DIR)/bin/python3 -W ignore::DeprecationWarning 11 | PIP = $(VENV_DIR)/bin/pip 12 | 13 | ################################################################################# 14 | # STANDARD COMMANDS # 15 | ################################################################################# 16 | 17 | ## Install Python Dependencies 18 | requirements: venv 19 | $(PIP) install -U pip setuptools wheel 20 | $(PIP) install -r requirements.txt 21 | 22 | ## Delete all compiled Python files 23 | clean: 24 | find . -type f -name "*.py[co]" -delete 25 | find . -type d -name "__pycache__" -delete 26 | 27 | ## Lint using flake8 28 | lint: 29 | @$(PYTHON_INTERPRETER) -m flake8 --config=$(PROJECT_DIR)/.flake8 src 30 | 31 | ## Install virtual environment 32 | venv: 33 | ifeq ($(wildcard $(VENV_DIR)/*),) 34 | @echo "Did not find $(VENV_DIR), creating..." 35 | mkdir -p $(VENV_DIR) 36 | python3 -m venv $(VENV_DIR) 37 | endif 38 | 39 | ################################################################################# 40 | # CUSTOM COMMANDS # 41 | ################################################################################# 42 | 43 | simulate: 44 | @$(PYTHON_INTERPRETER) src/$(PROJECT_NAME)/simulate.py 45 | 46 | events: 47 | @$(PYTHON_INTERPRETER) src/$(PROJECT_NAME)/analyse/run.py --option=events 48 | 49 | test: clean 50 | @echo "\n----------------------- Run Tests ------------------------------------ \n" 51 | @$(PYTHON_INTERPRETER) -m unittest -v src/$(PROJECT_NAME)/test/* -------------------------------------------------------------------------------- /pyapp/src/lem_sim/simulate.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from lem_sim import globalmemory as mem 4 | from lem_sim import linearoptimization as lp 5 | from lem_sim import utils 6 | from lem_sim import output 7 | 8 | 9 | def main(): 10 | 11 | # general setup of simulation 12 | var = mem.Variables() 13 | lp.decompose(var) 14 | 15 | # set initial inventory and market prices 16 | var.dealer.set_resource_inventory() 17 | var.dealer.set_mkt_prices() 18 | 19 | utils.wait_for_new_block(var) 20 | 21 | # set initial simulation parameter 22 | market_prices = var.dealer.mkt_prices 23 | equal_market_prices = False 24 | iteration = 1 25 | 26 | output.initial_setup(var, draw=True) 27 | 28 | # main simulation loop 29 | while(not equal_market_prices): 30 | 31 | for agent in var.agent_pool: 32 | agent.get_mkt_prices() # call 33 | agent.determine_bundle_attributes() 34 | agent.set_order() # transact 35 | 36 | utils.wait_for_new_block(var) 37 | 38 | var.dealer.get_orders() # call 39 | var.dealer.create_mmp() 40 | var.dealer.solve_mmp() 41 | var.dealer.set_trades() # transact 42 | var.dealer.delete_order() # transact 43 | var.dealer.set_mkt_prices() # transact 44 | var.dealer.set_mmp_attributes() # transact 45 | 46 | utils.wait_for_new_block(var) 47 | 48 | for agent in var.agent_pool: 49 | agent.verify_strong_duality() # call 50 | agent.accept_trade() # transact 51 | agent.add_trade_to_shared_resources() 52 | 53 | utils.wait_for_new_block(var) 54 | 55 | var.latest_block = var.web3.eth.getBlock('latest')['number'] 56 | 57 | var.dealer.recalculate_resource_inventory() # transact 58 | 59 | output.iteration_stats(var, iteration, draw=True) 60 | iteration += 1 61 | 62 | if(np.array_equal(market_prices, var.dealer.mkt_prices)): 63 | equal_market_prices = True 64 | else: 65 | market_prices = var.dealer.mkt_prices 66 | 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/linearoptimization/decompose.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from lem_sim import linearoptimization as lp 4 | 5 | 6 | def decompose(var): 7 | ''' 8 | d = target coefs 9 | n = individual resources 10 | N = individual coefs 11 | c = shared resources 12 | C = shared coefs ''' 13 | 14 | d, n, N, c, C = split_central_problem(var) 15 | N = remove_zero_rows_of_individual_coefs(N) 16 | 17 | for d_j, n_j, N_j, c_j, C_j, agent_j in zip(d, n, N, c, C, var.agent_pool): 18 | optimization_problem_j = lp.OptimizationProblem(d_j, n_j, N_j, c_j, C_j) 19 | agent_j.optimization_problem = optimization_problem_j 20 | 21 | 22 | def split_central_problem(var): 23 | amount_agents = var.amount_agents 24 | central_problem = var.central_problem 25 | 26 | d = np.split(central_problem.target_coefs, amount_agents, axis=0) 27 | n = np.split(central_problem.individual_resources, amount_agents, axis=0) 28 | N = np.split(central_problem.individual_coefs, amount_agents, axis=1) 29 | c = distribute_shared_resources(central_problem.shared_resources, amount_agents, var.dealer) 30 | C = np.split(central_problem.shared_coefs, amount_agents, axis=1) 31 | 32 | return d, n, N, c, C 33 | 34 | 35 | def remove_zero_rows_of_individual_coefs(N): 36 | return [N_j[~np.all(N_j == 0, axis=1)] for N_j in N] 37 | 38 | 39 | def distribute_shared_resources(c, amount_agents, dealer): 40 | ''' distribute the shared resources into equally sized parts ''' 41 | distributed_resources = [np.divide(c, amount_agents + 1) for i in range(amount_agents + 1)] 42 | distributed_resources = [np.around(array, decimals=2) for array in distributed_resources] 43 | 44 | # allocation of research paper 45 | # distributed_resources = [] 46 | # distributed_resources.append(np.array([3, 4])) 47 | # distributed_resources.append(np.array([4, 1])) 48 | # distributed_resources.append(np.array([1, 0])) 49 | 50 | dealer.resource_inventory = distributed_resources.pop(0) 51 | return distributed_resources 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEM Simulation 2 | **A blockchain-based local energy market (LEM) simulation.** 3 | 4 | ## Motivation of LEM 5 | The generation from distributed renewable energy sources (RES) is constantly increasing. In contrast to power plants which run by non-renewable fossil fuels, distributed RES produce energy in a decentralized and volatile way, which is hard to predict. These characteristics of the distributed RES challenge the current energy system. The existing electric grid is build for centralized generation by large power plants and the design of the current wholesale markets is not able to react in real-time to a significant amount of distributed RES. Therefore, new market approaches are needed, to successfully integrate the increasing amount of distributed RES. A possible solution to the technical and market problems is Peer-to-Peer (P2P) energy trading in local energy markets (LEM). 6 | LEM, also called microgrid energy markets, consist of small scale prosumers, consumers and a market platform which enables the trading of locally generated energy between the parties of a community. 7 | 8 | ## Requirements 9 | * Python 3.6 10 | * Truffle v5.0 11 | * Docker v18.0 12 | * GNU Make 13 | 14 | ## Installation 15 | 1. First of all, we have to start the local blockchain. For this type in: 16 | 17 | ```docker-compose up ganache``` 18 | 19 | 2. After that, we need to deploy the smart contract into the running local blockchain. Therefore, we open another terminal window and change the directory to: 20 | 21 | ```cd /path/to/lem_sim/dealer``` 22 | 23 | 3. To deploy the smart contract, type in the following expression: 24 | 25 | ```truffle migrate --network development``` 26 | 27 | 4. After these steps, the blockchain and the smart contract are ready for communication. 28 | Now, we move to the python application. Therefore, change the directory to: 29 | 30 | ```cd path/to/lem_sim/pyapp``` 31 | 32 | 5. Now, you need to set up the python virtual environment. Therefore, type in the following expression: 33 | 34 | ```make requirements``` 35 | 36 | 6. This expression will create a virtual environment in the folder ```pyapp/env``` and install all required python packages into this virtual environment. 37 | After the successful set up, type in the following expression to run the python simulation 38 | 39 | ```make simulate``` 40 | 41 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/globalmemory/variables.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from lem_sim import communication 4 | from lem_sim import client 5 | from lem_sim import contract 6 | from lem_sim import linearoptimization as lp 7 | 8 | ''' Definition of the Central Optimization Problem ''' 9 | TARGET_COEFS = np.array([-1, -2, -1, -3]) # cost vectors (d) 10 | INDIVIDUAL_RESOURCES = np.array([4, 9]) # individual resources (n) 11 | INDIVIDUAL_COEFS = np.array([[2, 1, 0, 0], [0, 0, 2, 3]]) # individual coefficients(N) 12 | SHARED_RESOURCES = np.array([8, 5]) # shared resources (c) 13 | SHARED_COEFS = np.array([[1, 3, 2, 1], [1, 1, 1, 1]]) # shared coefficients (C) 14 | 15 | 16 | class Variables(object): 17 | 18 | def __init__(self): 19 | self._central_problem = lp.OptimizationProblem(TARGET_COEFS, INDIVIDUAL_RESOURCES, INDIVIDUAL_COEFS, SHARED_RESOURCES, SHARED_COEFS) 20 | self._web3 = communication.get_network_connection() 21 | self._dealer_contract = contract.ContractHandler(self._web3, 'Dealer.json') 22 | self._accounts = self._web3.eth.accounts 23 | self._dealer = client.Dealer(self._accounts.pop(0), self._web3, self._dealer_contract, self._central_problem.shared_resources.size) 24 | self._agent_pool = [client.Agent(number, account, self._web3, self._dealer_contract) for number, account in enumerate(self._accounts, 1)] 25 | self._amount_agents = len(self._agent_pool) 26 | self._latest_block = self._web3.eth.getBlock('latest')['number'] 27 | 28 | @property 29 | def web3(self): 30 | return self._web3 31 | 32 | @property 33 | def accounts(self): 34 | return self._accounts 35 | 36 | @property 37 | def agent_pool(self): 38 | return self._agent_pool 39 | 40 | @property 41 | def amount_agents(self): 42 | return self._amount_agents 43 | 44 | @property 45 | def dealer_contract(self): 46 | return self._dealer_contract 47 | 48 | @property 49 | def dealer(self): 50 | return self._dealer 51 | 52 | @property 53 | def central_problem(self): 54 | return self._central_problem 55 | 56 | @property 57 | def latest_block(self): 58 | return self._latest_block 59 | 60 | @latest_block.setter 61 | def latest_block(self, value): 62 | self._latest_block = value 63 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_dealer_values.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | from lem_sim import utils 5 | from lem_sim import globalmemory as mem 6 | 7 | 8 | class DealerValuesTest(unittest.TestCase): 9 | 10 | def test_shift_decimal_places(self): 11 | bundle = [3.55, 4.66] 12 | value = 3 13 | 14 | int_bundle = utils.shift_decimal_right(bundle) 15 | float_bundle = utils.shift_decimal_left(int_bundle) 16 | 17 | int_value = utils.shift_decimal_right(value) 18 | float_value = utils.shift_decimal_left(int_value) 19 | 20 | self.assertEqual(bundle, float_bundle) 21 | self.assertEqual(int_bundle, [355, 466]) 22 | 23 | self.assertEqual(value, float_value) 24 | self.assertEqual(int_value, 300) 25 | 26 | def test_prepare_for_sending(self): 27 | array = np.array([1, 2, 3]) 28 | value = 1.56 29 | prepared_array = utils.prepare_for_sending(array) 30 | prepared_value = utils.prepare_for_sending(value) 31 | 32 | self.assertIsInstance(prepared_array, list) 33 | self.assertIsInstance(prepared_array[0], int) 34 | self.assertIsInstance(prepared_value, int) 35 | 36 | def test_prepare_for_storing(self): 37 | received_list = [100, 200, 300] 38 | received_value = 190000 39 | prepared_list = utils.prepare_for_storing(received_list) 40 | prepared_value = utils.prepare_for_storing(received_value) 41 | 42 | self.assertIsInstance(prepared_list, np.ndarray) 43 | self.assertIsInstance(prepared_list[0], float) 44 | self.assertIsInstance(prepared_value, float) 45 | 46 | def test_create_trades(self): 47 | first_order = ['account_1', [8, 3], 6] 48 | second_order = ['account_1', [2, 9], 3] 49 | third_order = ['account_2', [1, 4], 7] 50 | fourth_order = ['account_1', [5, 6], 4] 51 | 52 | var = mem.Variables() 53 | dealer = var.dealer 54 | dealer._mmp_values = np.array([1, 0, 1, 1]) 55 | dealer._order_handler = utils.OrderHandler() 56 | dealer._order_handler.add_order(0, first_order) 57 | dealer._order_handler.add_order(1, second_order) 58 | dealer._order_handler.add_order(2, third_order) 59 | dealer._order_handler.add_order(3, fourth_order) 60 | dealer.set_trade_share() 61 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/utils/order_handler.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from lem_sim import utils 4 | 5 | 6 | class OrderHandler(object): 7 | 8 | def __init__(self): 9 | self._pool = {} 10 | 11 | def add_order(self, order_id, order): 12 | account = order[0] 13 | bundle = utils.prepare_for_storing(order[1]) 14 | bid = utils.prepare_for_storing(order[2]) 15 | 16 | if account in self._pool.keys(): 17 | self._pool[account].add_order(bundle, bid, order_id) 18 | else: 19 | self._pool[account] = Orders(account) 20 | self._pool[account].add_order(bundle, bid, order_id) 21 | 22 | def get_all_accounts(self): 23 | return self._pool.keys() 24 | 25 | def get_orders_of_account(self, account): 26 | return self._pool[account] 27 | 28 | def get_all_orders(self): 29 | return [self._pool[account] for account in self._pool.keys()] 30 | 31 | 32 | class Orders(object): 33 | 34 | def __init__(self, account): 35 | self._account = account 36 | self._bundles = [] 37 | self._bids = [] 38 | self._indices = [] 39 | self._trade_share = [] 40 | self._trade = None 41 | self._prepayment = None 42 | self._bill = None 43 | self._refund = None 44 | 45 | @property 46 | def account(self): 47 | return self._account 48 | 49 | @property 50 | def bundles(self): 51 | return self._bundles 52 | 53 | @property 54 | def indices(self): 55 | return self._indices 56 | 57 | @property 58 | def trade(self): 59 | return self._trade 60 | 61 | @property 62 | def amount_orders(self): 63 | return len(self._bundles) 64 | 65 | @property 66 | def trade_share(self): 67 | return self._trade_share 68 | 69 | @trade_share.setter 70 | def trade_share(self, value): 71 | self._trade_share = value 72 | 73 | def add_order(self, bundle, bid, indice): 74 | self._bundles.append(bundle) 75 | self._bids.append(bid) 76 | self._indices.append(indice) 77 | 78 | def get_concatenated_bundles(self): 79 | return np.array(self._bundles).T 80 | 81 | def get_concatenated_bids(self): 82 | return np.array(self._bids) 83 | 84 | def calculate_trade(self): 85 | self._trade = sum([bundle * trade_share for bundle, trade_share in zip(self._bundles, self._trade_share)]) 86 | 87 | def calculate_bill(self, mkt_prices): 88 | self._bill = np.sum(np.multiply(self._trade, mkt_prices)) 89 | 90 | def calculate_prepayment(self): 91 | settled_trades = [1 for share in self._trade_share if share > 0] 92 | self._prepayment = sum([bid * settled_trade for bid, settled_trade in zip(self._bids, settled_trades)]) 93 | 94 | def calculate_refund(self): 95 | self._refund = self._prepayment - self._bill 96 | 97 | def get_trade_information(self): 98 | trade = utils.prepare_for_sending(self._trade) 99 | return self._account, trade, self._prepayment, self._bill, self._refund 100 | -------------------------------------------------------------------------------- /dealer/truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | development: { 46 | host: "0.0.0.0", // Localhost (default: none) 47 | port: 8545, // Standard Ethereum port (default: none) 48 | network_id: "*", // Any network (default: none) 49 | }, 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/${infuraKey}`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // timeout: 100000 83 | }, 84 | 85 | // Configure your compilers 86 | compilers: { 87 | solc: { 88 | version: "0.4.25", // Fetch exact version from solc-bin (default: truffle's version) 89 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 90 | // settings: { // See the solidity docs for advice about optimization and evmVersion 91 | // optimizer: { 92 | // enabled: false, 93 | // runs: 200 94 | // }, 95 | // evmVersion: "byzantium" 96 | // } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/test/test_linearoptimization.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from scipy.optimize import linprog 4 | from cvxopt import matrix, solvers 5 | 6 | from lem_sim import linearoptimization as lp 7 | 8 | 9 | class LinearOptimizationTest(unittest.TestCase): 10 | 11 | def test_solve_problem(self): 12 | target_coefs = np.array([1, -2, -1, -3]) 13 | constraint_coefs = np.array([[2, 1, 0, 0], [0, 0, 2, 3], [1, 3, 2, 1], [1, 1, 1, 1]]) 14 | constraint_bounds = np.array([4, 9, 8, 5]) 15 | 16 | self.assertIsNotNone(linprog(target_coefs, A_ub=constraint_coefs, b_ub=constraint_bounds)) 17 | 18 | def test_split_array_into_two(self): 19 | constraint_coefs = np.array([[2, 1, 0, 0], [0, 0, 2, 3], [1, 3, 2, 1], [1, 1, 1, 1]]) 20 | splitted_constraint_coefs = np.split(constraint_coefs, 2, axis=1) 21 | array = np.array([[2, 1], [0, 0], [1, 3], [1, 1]]) 22 | 23 | self.assertTrue(np.array_equal(splitted_constraint_coefs[0], array)) 24 | 25 | def test_split_array_into_four(self): 26 | constraint_coefs = np.array([[2, 1, 0, 0], [0, 0, 2, 3], [1, 3, 2, 1], [1, 1, 1, 1]]) 27 | splitted_constraint_coefs = np.split(constraint_coefs, 4, axis=1) 28 | array = np.array([[2], [0], [1], [1]]) 29 | 30 | self.assertTrue(np.array_equal(splitted_constraint_coefs[0], array)) 31 | 32 | def test_solve_bundle_determination(self): 33 | TARGET_COEFS = np.array([-1, -2]) # cost vectors (d) 34 | INDIVIDUAL_RESOURCES = np.array([4]) # individual resources (n) 35 | INDIVIDUAL_COEFS = np.array([[2, 1]]) # individual coefficients(N) 36 | SHARED_RESOURCES = np.array([4, 1]) # shared resources (c) 37 | SHARED_COEFS = np.array([[1, 3], [1, 1]]) # shared coefficients (C) 38 | 39 | optimization_problem = lp.OptimizationProblem(TARGET_COEFS, INDIVIDUAL_RESOURCES, INDIVIDUAL_COEFS, SHARED_RESOURCES, SHARED_COEFS) 40 | bundle_size = optimization_problem.shared_resources.size 41 | 42 | bundle_target_coefs = np.concatenate((optimization_problem.target_coefs, np.zeros(bundle_size))) 43 | bundle_individual_coefs = np.concatenate((optimization_problem.individual_coefs, np.zeros(optimization_problem.individual_coefs.shape)), axis=1) 44 | bundle_shared_coefs = np.concatenate((optimization_problem.shared_coefs, np.identity(optimization_problem.shared_resources.size, dtype=float) * (-1)), axis=1) 45 | 46 | bundle_coefs = np.concatenate((bundle_individual_coefs, bundle_shared_coefs)) 47 | bundle_resources = np.concatenate((INDIVIDUAL_RESOURCES, SHARED_RESOURCES)) 48 | 49 | result = linprog(bundle_target_coefs, bundle_coefs, bundle_resources) 50 | self.assertTrue(np.array_equal(result.x[-bundle_size:], np.array([8, 3]))) 51 | 52 | def test_mmp(self): 53 | TARGET_COEFS = np.array([-6, -9]) 54 | CONSTRAINT_COEFS = np.array([[8, 2], [3, 3]]) 55 | CONSTRAINT_BOUNDS = np.array([3, 4]) 56 | COEF_BOUNDS = ((0, 1), (0, 1)) 57 | result = linprog(TARGET_COEFS, A_ub=CONSTRAINT_COEFS, b_ub=CONSTRAINT_BOUNDS, bounds=COEF_BOUNDS) 58 | self.assertTrue(np.array_equal(result['x'], np.array([0.125, 1.]))) 59 | 60 | def test_mmp_dual(self): 61 | TARGET_COEFS = np.array([-6, -9], dtype=float) 62 | CONSTRAINT_COEFS = np.array([[8, 2], [3, 3], [1, 0], [0, 1]], dtype=float) 63 | CONSTRAINT_BOUNDS = np.array([3, 4, 1, 1], dtype=float) 64 | 65 | A = matrix(CONSTRAINT_COEFS) 66 | b = matrix(CONSTRAINT_BOUNDS) 67 | c = matrix(TARGET_COEFS) 68 | # solvers.options['show_progress'] = False 69 | sol = solvers.lp(c, A, b) 70 | 71 | y_1 = float('%.5f' % (sol['x'][0])) 72 | y_2 = float('%.5f' % (sol['x'][1])) 73 | mkt_price_w1 = float('%.5f' % (sol['z'][0])) 74 | mkt_price_w2 = float('%.5f' % (sol['z'][1])) 75 | 76 | self.assertEqual([y_1, y_2, mkt_price_w1, mkt_price_w2], [0.12500, 1.00000, 0.75000, 0.00000]) 77 | 78 | def test_mmp_bugfix(self): 79 | TARGET_COEFS = np.array([-0.5, 0], dtype=float) 80 | CONSTRAINT_COEFS = np.array([[1, 0], [0, 0], [1, 0], [0, 1], [-1, 0], [0, -1]], dtype=float) 81 | CONSTRAINT_BOUNDS = np.array([0, 0, 1, 1, 0, 0], dtype=float) 82 | 83 | A = matrix(CONSTRAINT_COEFS) 84 | b = matrix(CONSTRAINT_BOUNDS) 85 | c = matrix(TARGET_COEFS) 86 | solvers.options['show_progress'] = False 87 | sol = solvers.lp(c, A, b) 88 | 89 | self.assertIsNotNone(sol) 90 | 91 | def test_mmp_bugfix_second(self): 92 | TARGET_COEFS = np.array([3.5, -4.5, 1.5], dtype=float) 93 | CONSTRAINT_COEFS = np.array([[-5, 7, 3], [-2, 2, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, -1]], dtype=float) 94 | CONSTRAINT_BOUNDS = np.array([0, 0, 1, 1, 1, 0, 0, 0], dtype=float) 95 | 96 | A = matrix(CONSTRAINT_COEFS) 97 | b = matrix(CONSTRAINT_BOUNDS) 98 | c = matrix(TARGET_COEFS) 99 | solvers.options['show_progress'] = False 100 | sol = solvers.lp(c, A, b) 101 | 102 | self.assertIsNotNone(sol) 103 | 104 | def test_strong_duality(self): 105 | ''' 106 | cT * x = yT * b 107 | 108 | c = target coefs of mmp 109 | x = values of mmp 110 | y = duals of mmp (market prices) 111 | b = bounds of mmp (resource_inventory) 112 | ''' 113 | 114 | TARGET_COEFS = np.array([-6, -9], dtype=float) 115 | CONSTRAINT_COEFS = np.array([[8, 2], [3, 3], [1, 0], [0, 1]], dtype=float) 116 | CONSTRAINT_BOUNDS = np.array([3, 4, 1, 1], dtype=float) 117 | 118 | A = matrix(CONSTRAINT_COEFS) 119 | b = matrix(CONSTRAINT_BOUNDS) 120 | c = matrix(TARGET_COEFS) 121 | # solvers.options['show_progress'] = False 122 | sol = solvers.lp(c, A, b) 123 | 124 | mmp_values = np.array([float('%.5f' % entry) for entry in sol['x']]).T 125 | dual_values = np.array([float('%.5f' % entry) for entry in sol['z']]).T 126 | 127 | print(np.sum(TARGET_COEFS * mmp_values)) 128 | print(np.sum(dual_values * CONSTRAINT_BOUNDS)) 129 | 130 | 131 | if __name__ == '__main__': 132 | unittest.main() 133 | -------------------------------------------------------------------------------- /dealer/contracts/Dealer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.25; 2 | 3 | /* 4 | 1 wei = 1.000.000.000.000.000.000 ether (10^18) 5 | */ 6 | 7 | contract Dealer{ 8 | // general properties 9 | address private _owner; 10 | int256[] public resource_inventory; 11 | int256[] public mkt_prices; 12 | 13 | // attributes for order handling 14 | uint32 public order_count; 15 | uint32[] public order_indices; 16 | mapping(uint32 => Order) public orders; 17 | 18 | // attributes for trade handling 19 | 20 | // mapping from address to position in trades_accounts array 21 | // 0 means the address in not in the array 22 | mapping (address => uint256) account_index; 23 | address[] public trades_accounts; 24 | mapping(address => int256[]) public trades; 25 | mapping(address => uint256) public bills; 26 | mapping(address => uint256) public prepayments; 27 | mapping(address => uint256) public refunds; 28 | 29 | // attributes for the mmp 30 | int256[] public mmp_duals; 31 | int256[] public mmp_values; 32 | int256[] public mmp_target_coefs; 33 | int256[] public mmp_bounds; 34 | 35 | // events 36 | event ReceivedOrder(address from, int256[] bundle, uint256 bid, uint256 prepayment, uint32 index); 37 | event DeletedOrder(uint32 index); 38 | event StoredTrade(address receiver, int256[] trade, uint256 prepayment, uint256 bill, uint256 refund); 39 | event FetchedTrade(address receiver, int256[] trade, uint256 refund); 40 | event RejectedTrade(address receiver, int256[] trade, uint256 refund); 41 | 42 | constructor() public { 43 | _owner = msg.sender; 44 | order_count = 1; 45 | trades_accounts.push(0x0); 46 | } 47 | 48 | /* 49 | modifier 50 | */ 51 | 52 | modifier checkPrepayment(uint256 _prepayment) { 53 | require( 54 | _prepayment == msg.value, 55 | "Not enough wei" 56 | ); 57 | _; 58 | } 59 | 60 | modifier onlyByOwner() { 61 | require( 62 | msg.sender == _owner, 63 | "Sender not authorized" 64 | ); 65 | _; 66 | } 67 | 68 | /* 69 | custom structs 70 | */ 71 | 72 | struct Order { 73 | address account; 74 | int256[] bundle; 75 | uint256 bid; 76 | } 77 | 78 | /* 79 | functions for trade handling 80 | */ 81 | 82 | function addToArray(address who) { 83 | if(!inArray(who)) { 84 | //append 85 | account_index[who] = trades_accounts.length; 86 | trades_accounts.push(who); 87 | } 88 | } 89 | 90 | function inArray(address who) public view returns (bool) { 91 | if(who != 0x0 && account_index[who] > 0) { 92 | return true; 93 | } 94 | return false; 95 | } 96 | 97 | function setTrade(address _account, int256[] _trade, uint256 _prepayment, uint256 _bill, uint256 _refund) public onlyByOwner() { 98 | addToArray(_account); 99 | trades[_account] = _trade; 100 | bills[_account] = _bill; 101 | prepayments[_account] = _prepayment; 102 | refunds[_account] = _refund; 103 | 104 | emit StoredTrade(_account, _trade, _prepayment, _bill, _refund); 105 | } 106 | 107 | function getTrade(address account) public returns (int256[]) { 108 | return trades[account]; 109 | 110 | } 111 | 112 | function acceptTrade(bool accept_trade) public returns (int256[]) { 113 | if(accept_trade) { 114 | msg.sender.transfer(refunds[msg.sender]); 115 | emit FetchedTrade(msg.sender, trades[msg.sender], refunds[msg.sender]); 116 | return trades[msg.sender]; 117 | } else { 118 | msg.sender.transfer(prepayments[msg.sender]); 119 | delete trades[msg.sender]; 120 | emit RejectedTrade(msg.sender, trades[msg.sender], prepayments[msg.sender]); 121 | } 122 | } 123 | 124 | /* 125 | functions for order handling 126 | */ 127 | 128 | function setOrder(int256[] _bundle, uint256 _bid, uint256 _prepayment) public payable checkPrepayment(_prepayment) { 129 | 130 | Order memory new_order = Order( 131 | msg.sender, 132 | _bundle, 133 | _bid 134 | ); 135 | 136 | orders[order_count] = new_order; 137 | order_indices.push(order_count); 138 | 139 | emit ReceivedOrder(new_order.account, new_order.bundle, new_order.bid, _prepayment, order_count); 140 | 141 | // increment order count 142 | order_count ++; 143 | } 144 | 145 | function getOrder(uint32 order_id) public view returns (address, int256[], uint256) { 146 | return (orders[order_id].account, orders[order_id].bundle, orders[order_id].bid); 147 | } 148 | 149 | function deleteOrder(uint32 index) public onlyByOwner() { 150 | delete orders[index]; 151 | delete order_indices[index - 1]; 152 | 153 | emit DeletedOrder(index); 154 | } 155 | 156 | /* 157 | getter and setter for mkt prices 158 | */ 159 | 160 | function setMktPrices(int256[] _mkt_prices) public onlyByOwner() { 161 | mkt_prices = _mkt_prices; 162 | } 163 | 164 | function getMktPrices() public view returns (int256[]) { 165 | return mkt_prices; 166 | } 167 | 168 | /* 169 | functions for managing resource inventory 170 | */ 171 | 172 | function setResourceInventory (int256[] _resource_inventory) public onlyByOwner() { 173 | resource_inventory = _resource_inventory; 174 | } 175 | 176 | function getResourceInventory() public view returns (int256[]) { 177 | return resource_inventory; 178 | } 179 | 180 | /* 181 | getter and setter for mmp attributes 182 | */ 183 | 184 | function setMMPAttributes(int256[] _mmp_values, int256[] _mmp_duals, int256[] _mmp_target_coefs, int256[] _mmp_bounds) public onlyByOwner() { 185 | mmp_values = _mmp_values; 186 | mmp_target_coefs = _mmp_target_coefs; 187 | mmp_bounds = _mmp_bounds; 188 | mmp_duals = _mmp_duals; 189 | } 190 | 191 | function getMMPAttributes() public view returns (int256[], int256[], int256[], int256[]) { 192 | return (mmp_values, mmp_duals, mmp_target_coefs, mmp_bounds); 193 | } 194 | 195 | /* 196 | miscellaneous getter and setter 197 | */ 198 | 199 | function getOwner() public view returns (address) { 200 | return _owner; 201 | } 202 | 203 | function getBill() public view returns (uint256) { 204 | return bills[msg.sender]; 205 | } 206 | 207 | function getOrderIndices() public view returns (uint32[]) { 208 | return order_indices; 209 | } 210 | 211 | function getTradesAccounts() public view returns (address[]) { 212 | return trades_accounts; 213 | } 214 | 215 | } -------------------------------------------------------------------------------- /pyapp/src/lem_sim/client/agent.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import linprog 3 | import math 4 | 5 | from lem_sim import utils 6 | 7 | 8 | class Agent(object): 9 | 10 | def __init__(self, agent_number, account_address, web3, dealer_contract): 11 | self._name = 'AGENT{}'.format(agent_number) 12 | self._account_address = account_address 13 | self._web3 = web3 14 | self._dealer_contract = dealer_contract 15 | self._optimization_problem = None 16 | self._bundle_set = None 17 | self._bid = None 18 | self._mkt_prices = None 19 | self._trade = None 20 | self._objective = None 21 | self._wealth = None 22 | self._accept_trade = None 23 | 24 | ''' getter and setter of class attributes ''' 25 | 26 | @property 27 | def name(self): 28 | return self._name 29 | 30 | @property 31 | def objective(self): 32 | return self._objective 33 | 34 | @property 35 | def bill(self): 36 | return self._bill 37 | 38 | @property 39 | def mkt_prices(self): 40 | return self._mkt_prices 41 | 42 | @property 43 | def trade(self): 44 | return self._trade 45 | 46 | @property 47 | def account_address(self): 48 | return self._account_address 49 | 50 | @property 51 | def balance(self): 52 | balance = self._web3.eth.getBalance(self._account_address) 53 | return float(utils.from_wei_to_ether(balance)) 54 | 55 | @property 56 | def bundle_set(self): 57 | return self._bundle_set 58 | 59 | @bundle_set.setter 60 | def bundle_set(self, value): 61 | self._bundle_set = value 62 | 63 | @property 64 | def bid(self): 65 | return self._bid 66 | 67 | @bid.setter 68 | def bid(self, value): 69 | self._bid = value 70 | 71 | @property 72 | def optimization_problem(self): 73 | return self._optimization_problem 74 | 75 | @optimization_problem.setter 76 | def optimization_problem(self, value): 77 | self._optimization_problem = value 78 | self._objective = self._optimization_problem.solve().fun 79 | self._wealth = self.balance + abs(self._objective) 80 | 81 | ''' functions for contract communication ''' 82 | 83 | def accept_trade(self): 84 | if(self._accept_trade): 85 | trade = self._dealer_contract.contract.functions.acceptTrade(self._accept_trade).call({'from': self._account_address}) 86 | self._trade = utils.prepare_for_storing(trade) 87 | 88 | self._dealer_contract.contract.functions.acceptTrade(self._accept_trade).transact({'from': self._account_address}) 89 | 90 | def set_order(self): 91 | bundle_set = utils.prepare_for_sending(self._bundle_set) 92 | bid = utils.prepare_for_sending(self._bid) 93 | 94 | if(bid > 0): 95 | prepayment = utils.from_ether_to_wei(self._bid) 96 | else: 97 | prepayment = 0 98 | 99 | tx_hash = self._dealer_contract.contract.functions.setOrder(bundle_set, bid, prepayment).transact({'from': self._account_address, 'value': prepayment}) 100 | 101 | def get_mkt_prices(self): 102 | mkt_prices = self._dealer_contract.contract.functions.getMktPrices().call() 103 | self._mkt_prices = utils.prepare_for_storing(mkt_prices) 104 | 105 | ''' functions to determine inner class attributes ''' 106 | 107 | def determine_bundle_attributes(self): 108 | result = solve_bundle_determination(self._optimization_problem, self._mkt_prices) 109 | self._bundle_set = result.x 110 | self._bid = self._objective - result.fun 111 | 112 | def get_mmp_attributes(self): 113 | mmp_attributes = self._dealer_contract.contract.functions.getMMPAttributes().call() 114 | mmp_values = utils.prepare_for_storing(mmp_attributes[0]) 115 | mmp_duals = utils.prepare_for_storing(mmp_attributes[1]) 116 | mmp_target_coefs = utils.prepare_for_storing(mmp_attributes[2]) 117 | mmp_bounds = utils.prepare_for_storing(mmp_attributes[3]) 118 | 119 | return mmp_values, mmp_duals, mmp_target_coefs, mmp_bounds 120 | 121 | def verify_strong_duality(self): 122 | mmp_values, mmp_duals, mmp_target_coefs, mmp_bounds = self.get_mmp_attributes() 123 | primal_bound = (-np.sum(mmp_values * mmp_target_coefs)) 124 | dual_bound = np.sum(mmp_duals * mmp_bounds) 125 | primal_bound = math.floor(primal_bound * 10) / 10 126 | dual_bound = math.floor(dual_bound * 10) / 10 127 | 128 | if primal_bound == dual_bound: 129 | self._accept_trade = True 130 | else: 131 | self._accept_trade = False 132 | 133 | 134 | def add_trade_to_shared_resources(self): 135 | # only add trade to shared resources and recalculate objective and bill if trade accepted 136 | if(self._accept_trade): 137 | self._optimization_problem.shared_resources = np.add(self._optimization_problem.shared_resources, self._trade) 138 | self._objective = self._optimization_problem.solve().fun # calculate new objective after getting new shared resources 139 | self._wealth = self.balance + abs(self._objective) # calculate new wealth after new objective calculation 140 | 141 | def __str__(self): 142 | class_str = '\n{}\naccount: {}\nbalance: {} ether\nobjective: {}\nwealth: {}\norder: {}\nbid: {} ether\ntrade: {}\ntrade accepted: {} \nallocation: {}'.format( 143 | self._name, 144 | self._account_address, 145 | self.balance, 146 | self._objective, 147 | self._wealth, 148 | self._bundle_set, 149 | self._bid, 150 | self._trade, 151 | self._accept_trade, 152 | self.optimization_problem.shared_resources 153 | ) 154 | 155 | return class_str 156 | 157 | 158 | def solve_bundle_determination(optimization_problem, mkt_prices): 159 | bundle_target_coefs = np.concatenate((optimization_problem.target_coefs, mkt_prices)) 160 | 161 | bundle_individual_coefs = np.concatenate((optimization_problem.individual_coefs, np.zeros(optimization_problem.individual_coefs.shape)), axis=1) 162 | bundle_shared_coefs = np.concatenate((optimization_problem.shared_coefs, np.identity(mkt_prices.size, dtype=float) * (-1)), axis=1) 163 | 164 | bundle_var_geq_zero_constraint = np.concatenate((np.identity(mkt_prices.size) * (-1), np.zeros(optimization_problem.shared_coefs.shape)), axis=1) 165 | bundle_var_geq_zero_bound = np.zeros(mkt_prices.size) 166 | 167 | bundle_coefs = np.concatenate((bundle_individual_coefs, bundle_shared_coefs, bundle_var_geq_zero_constraint)) 168 | bundle_resources = np.concatenate((optimization_problem.individual_resources, optimization_problem.shared_resources, bundle_var_geq_zero_bound)) 169 | 170 | result = linprog(bundle_target_coefs, bundle_coefs, bundle_resources) 171 | result.x = result.x[- mkt_prices.size:] # remove not bundle coefficients 172 | 173 | return result 174 | -------------------------------------------------------------------------------- /pyapp/src/lem_sim/client/dealer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from cvxopt import matrix, solvers 3 | 4 | from lem_sim import utils 5 | 6 | 7 | class Dealer(object): 8 | 9 | def __init__(self, account_address, web3, dealer_contract, shared_resource_size): 10 | self._account_address = account_address 11 | self._web3 = web3 12 | self._dealer_contract = dealer_contract 13 | self._resource_inventory = None 14 | self._trade = None 15 | self._shared_resource_size = shared_resource_size 16 | self._mkt_prices = np.zeros(self._shared_resource_size) 17 | self._order_handler = None 18 | 19 | self._mmp_target_coefs = None 20 | self._mmp_constraint_coefs = None 21 | self._mmp_constraint_bounds = None 22 | self._mmp_amount_variables = None 23 | self._mmp_values = None 24 | self._mmp_duals = None 25 | 26 | ''' getter and setter of class attributes ''' 27 | 28 | @property 29 | def account_address(self): 30 | return self._account_address 31 | 32 | @property 33 | def mkt_prices(self): 34 | return self._mkt_prices 35 | 36 | @mkt_prices.setter 37 | def mkt_prices(self, value): 38 | self._mkt_prices = value 39 | 40 | @property 41 | def resource_inventory(self): 42 | return self._resource_inventory 43 | 44 | @resource_inventory.setter 45 | def resource_inventory(self, value): 46 | self._resource_inventory = value 47 | 48 | ''' functions for contract communication ''' 49 | 50 | def set_mkt_prices(self): 51 | mkt_prices = utils.prepare_for_sending(self._mkt_prices) 52 | self._dealer_contract.contract.functions.setMktPrices(mkt_prices).transact({'from': self._account_address}) 53 | 54 | def set_resource_inventory(self): 55 | resource_inventory = utils.prepare_for_sending(self._resource_inventory) 56 | self._dealer_contract.contract.functions.setResourceInventory(resource_inventory).transact({'from': self._account_address}) 57 | 58 | def get_resource_inventory(self): 59 | resource_inventory = self._dealer_contract.contract.functions.getResourceInventory().call() 60 | return utils.prepare_for_storing(resource_inventory) 61 | 62 | def recalculate_resource_inventory(self): 63 | self.calculate_own_trade() # calculate dealers own trade out of all accepted trades 64 | resource_inventory = self.get_resource_inventory() # get resource inventory which is stored in contract 65 | self._resource_inventory = utils.truncate_values_of_array(resource_inventory - self._trade) # calculate new inventory 66 | self.set_resource_inventory() # store new inventory in contract 67 | 68 | def get_order_indices(self): 69 | return list(filter(None, self._dealer_contract.contract.functions.getOrderIndices().call())) 70 | 71 | def get_orders(self): 72 | self._order_handler = utils.OrderHandler() 73 | order_indices = self.get_order_indices() 74 | 75 | # get all orders from contract and store in order handler 76 | for order_id in order_indices: 77 | order = self._dealer_contract.contract.functions.getOrder(order_id).call() 78 | self._order_handler.add_order(order_id, order) 79 | 80 | def delete_order(self): 81 | settled_orders = self.get_settled_order_indices() 82 | for order_id in settled_orders: 83 | self._dealer_contract.contract.functions.deleteOrder(order_id).transact({'from': self._account_address, 'gas': 300000}) 84 | 85 | def calculate_own_trade(self): 86 | trades_accounts = self._dealer_contract.contract.functions.getTradesAccounts().call() # get lookup table of all accounts which have a trade 87 | trades = [self._dealer_contract.contract.functions.getTrade(account).call() for account in trades_accounts] 88 | trades = [utils.prepare_for_storing(trade) for trade in trades if trade] 89 | trades = [np.array(trade) for trade in trades] 90 | self._trade = sum(trades) 91 | 92 | def set_trades(self): 93 | self.set_trade_share() 94 | self.initiate_trade_calculation() 95 | self.initiate_prepayment_calculation() 96 | self.initiate_bill_calculation() 97 | self.initiate_refund_calculation() 98 | 99 | for order in self._order_handler.get_all_orders(): 100 | account, trade, prepayment, bill, refund = order.get_trade_information() 101 | prepayment = utils.from_ether_to_wei(prepayment) 102 | bill = utils.from_ether_to_wei(bill) 103 | refund = utils.from_ether_to_wei(refund) 104 | 105 | self._dealer_contract.contract.functions.setTrade(account, trade, prepayment, bill, refund).transact({'from': self._account_address}) 106 | 107 | def set_trade_share(self): 108 | amount_orders = [order.amount_orders for order in self._order_handler.get_all_orders()] 109 | trade_share = self._mmp_values 110 | for amount, orders in zip(amount_orders, self._order_handler.get_all_orders()): 111 | orders.trade_share = trade_share[0:amount] 112 | trade_share = trade_share[amount:] 113 | 114 | def initiate_trade_calculation(self): 115 | for order in self._order_handler.get_all_orders(): 116 | order.calculate_trade() 117 | 118 | def initiate_prepayment_calculation(self): 119 | for order in self._order_handler.get_all_orders(): 120 | order.calculate_prepayment() 121 | 122 | def initiate_bill_calculation(self): 123 | for order in self._order_handler.get_all_orders(): 124 | order.calculate_bill(self._mkt_prices) 125 | 126 | def initiate_refund_calculation(self): 127 | for order in self._order_handler.get_all_orders(): 128 | order.calculate_refund() 129 | 130 | def get_settled_order_indices(self): 131 | settled_orders = [] 132 | 133 | for order in self._order_handler.get_all_orders(): 134 | indice_of_trade_shares_not_zero = (order.trade_share.nonzero()[0].tolist()) 135 | indice_of_settled_orders = [order.indices[index] for index in indice_of_trade_shares_not_zero] 136 | settled_orders += indice_of_settled_orders 137 | 138 | return settled_orders 139 | 140 | def set_mmp_attributes(self): 141 | mmp_values = utils.prepare_for_sending(self._mmp_values) 142 | mkt_prices = utils.prepare_for_sending(self._mmp_duals) 143 | mmp_target_coefs = utils.prepare_for_sending(self._mmp_target_coefs) 144 | mmp_constraint_bounds = utils.prepare_for_sending(self._mmp_constraint_bounds) 145 | self._dealer_contract.contract.functions.setMMPAttributes(mmp_values, mkt_prices, mmp_target_coefs, mmp_constraint_bounds).transact({'from': self._account_address}) 146 | 147 | ''' functions for handling and solving mmp ''' 148 | 149 | def create_mmp(self): 150 | bundles = [order.get_concatenated_bundles() for order in self._order_handler.get_all_orders()] 151 | bids = [order.get_concatenated_bids() for order in self._order_handler.get_all_orders()] 152 | 153 | try: 154 | TARGET_COEFS = np.hstack(bids) * (-1) # create target coef vector 155 | 156 | self._mmp_amount_variables = np.size(TARGET_COEFS) # set amount of variables 157 | mmp_coefs = np.hstack(bundles) 158 | var_leq_one_coefs = np.identity(self._mmp_amount_variables, dtype=float) # create constraint matrix for y<=1 159 | var_geq_zero_coefs = np.identity(self._mmp_amount_variables, dtype=float) * (-1) # create constraint matrix for y>=0 160 | mmp_bounds = self._resource_inventory 161 | var_leq_one_bounds = np.ones(self._mmp_amount_variables, dtype=float) 162 | var_geq_zero_bounds = np.zeros(self._mmp_amount_variables, dtype=float) 163 | 164 | CONSTRAINT_COEFS = np.concatenate((mmp_coefs, var_leq_one_coefs, var_geq_zero_coefs), axis=0) # create final constraint matrix 165 | CONSTRAINT_BOUNDS = np.concatenate((mmp_bounds, var_leq_one_bounds, var_geq_zero_bounds)) # create final bounds matrix 166 | 167 | self._mmp_constraint_coefs = CONSTRAINT_COEFS 168 | self._mmp_constraint_bounds = CONSTRAINT_BOUNDS 169 | self._mmp_target_coefs = TARGET_COEFS 170 | 171 | except ValueError as error: 172 | print('Creation of MMP failed!') 173 | print(error) 174 | 175 | def solve_mmp(self): 176 | solvers.options['show_progress'] = False 177 | sol = solvers.lp(matrix(self._mmp_target_coefs), matrix(self._mmp_constraint_coefs), matrix(self._mmp_constraint_bounds)) 178 | self._mmp_values = np.array([float('%.2f' % (sol['x'][i])) for i in range(self._mmp_amount_variables)]) 179 | self._mmp_duals = np.array([float('%.2f' % entry) for entry in sol['z']]) 180 | self._mkt_prices = np.array([float('%.2f' % (sol['z'][i])) for i in range(self._shared_resource_size)]) 181 | 182 | def __str__(self): 183 | class_str = '\nDEALER\naccount: {}\ndealer inventory: {}\ndealer trade: {}\nmarket price: {}'.format( 184 | self._account_address, 185 | self._resource_inventory, 186 | self._trade, 187 | self._mkt_prices, 188 | ) 189 | return class_str 190 | --------------------------------------------------------------------------------