model.py
1 #!/usr/bin/env python3 2 3 """ 4 model.py: agent-based model of xSD system behavior, against a testnet 5 """ 6 from subprocess import Popen 7 import subprocess 8 import json 9 import collections 10 import random 11 import math 12 import logging 13 import time 14 import sys 15 import os 16 import base64 17 import mmap 18 from eth_abi import encode_single, decode_single 19 from web3 import Web3 20 import datetime 21 22 IS_DEBUG = False 23 is_try_model_mine = False 24 max_accounts = 40 25 block_offset = 19 + max_accounts 26 tx_pool_latency = 0.25 27 28 DEADLINE_FROM_NOW = 60 * 60 * 24 * 7 * 52 29 UINT256_MAX = 2**256 - 1 30 ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 31 MMAP_FILE = '/tmp/avax-cchain-nonces' 32 33 deploy_data = None 34 with open("deploy_output.txt", 'r+') as f: 35 deploy_data = f.read() 36 37 logger = logging.getLogger(__name__) 38 #provider = Web3.HTTPProvider('http://127.0.0.1:7545/ext/bc/C/rpc', request_kwargs={"timeout": 60*300}) 39 provider = Web3.WebsocketProvider('ws://127.0.0.1:9545/ext/bc/C/ws', websocket_timeout=60*300) 40 41 providerAvax = Web3.HTTPProvider('http://127.0.0.1:9545/ext/bc/C/avax', request_kwargs={"timeout": 60*300}) 42 w3 = Web3(provider) 43 from web3.middleware import geth_poa_middleware 44 w3.middleware_onion.inject(geth_poa_middleware, layer=0) 45 46 w3.eth.defaultAccount = w3.eth.accounts[0] 47 logger.info(w3.eth.blockNumber) 48 logger.info(w3.clientVersion) 49 #sys.exit() 50 51 TPRO = { 52 "addr": '', 53 "deploy_slug": "timeProviderAddress is at: " 54 } 55 56 EXCHG = { 57 "addr": '', 58 "decimals": 18, 59 "deploy_slug": "OptionsExchangeAddress is at: " 60 } 61 62 PROPSMNGERHELPER = { 63 "addr":"0x04AbCcEEd429062b0b145e85a91ee2D529b2840A" 64 } 65 66 PROPSWRPR = { 67 "addr": Web3.toChecksumAddress("0xbe9a38abe9bb3c28b164f1b0f9aeca9591cb8fb1") 68 } 69 70 PROPSMNGER = { 71 "addr": '0x6fe9afFFc3ffa4D59638486bbD029d0138D6E0af', 72 "deploy_slug": "OptionsExchangeAddress is at: " 73 } 74 75 CREDPRO = { 76 "addr": '', 77 "deploy_slug": "CreditProviderAddress is at: " 78 } 79 80 STG = { 81 "addr": '', 82 "deploy_slug": "ProtocolSettingsAddress is at: " 83 } 84 85 DPLY = { 86 "addr": '', 87 "deploy_slug": "Deployer4 is at: " 88 } 89 90 # USE FROM XSD SIMULATION 91 USDT = { 92 "addr": '0x351D75454225010b2d2EeBd0E96762291661CDcB', 93 "decimals": 6, 94 "symbol": 'USDT', 95 } 96 97 LLPF = { 98 "addr": '', 99 "deploy_slug": "LinearLiquidityPoolFactoryAddress is at: " 100 } 101 102 BTCUSDAgg = { 103 "addr": '', 104 "decimals": 8, 105 "symbol": 'BTCUSD', 106 "deploy_slug": "BTCUSDAgg is at: " 107 } 108 109 BTCUSDc = { 110 "addr": '', 111 "decimals": 18, 112 "symbol": 'BTCUSDc', 113 "deploy_slug": "BTCUSDMockFeed is at: " 114 } 115 116 for contract in [BTCUSDc, BTCUSDAgg, LLPF, STG, CREDPRO, EXCHG, TPRO]: 117 logger.info(contract["deploy_slug"]) 118 contract["addr"] = deploy_data.split(contract["deploy_slug"])[1].split('\n')[0] 119 logger.info('\t'+contract["addr"]) 120 121 122 # token (from Deploy Root on testnet) 123 xSD = { 124 "addr": '', 125 "decimals": 18, 126 "symbol": 'xSD', 127 } 128 129 AggregatorV3MockContract = json.loads(open('./build/contracts/AggregatorV3Mock.json', 'r+').read()) 130 ChainlinkFeedContract = json.loads(open('./build/contracts/ChainlinkFeed.json', 'r+').read()) 131 CreditProviderContract = json.loads(open('./build/contracts/CreditProvider.json', 'r+').read()) 132 OptionsExchangeContract = json.loads(open('./build/contracts/OptionsExchange.json', 'r+').read()) 133 ProposalManagerContract = json.loads(open('./build/contracts/ProposalsManager.json', 'r+').read()) 134 135 ProposalManagerHelperContract = json.loads(open('./build/contracts/PoolManagementProposal.json', 'r+').read()) 136 ProposalWrapperContract = json.loads(open('./build/contracts/ProposalWrapper.json', 'r+').read()) 137 138 USDTContract = json.loads(open('./build/contracts/TestnetUSDT.json', 'r+').read()) 139 OptionTokenContract = json.loads(open('./build/contracts/OptionToken.json', 'r+').read()) 140 ProtocolSettingsContract = json.loads(open('./build/contracts/ProtocolSettings.json', 'r+').read()) 141 LinearLiquidityPoolContract = json.loads(open('./build/contracts/LinearLiquidityPool.json', 'r+').read()) 142 LinearLiquidityPoolFactoryContract = json.loads(open('./build/contracts/LinearLiquidityPoolFactory.json', 'r+').read()) 143 ERC20StableCoinContract = json.loads(open('./build/contracts/ERC20.json', 'r+').read()) 144 TimeProviderMockContract = json.loads(open('./build/contracts/TimeProviderMock.json', 'r+').read()) 145 146 147 def get_addr_from_contract(contract): 148 return contract["networks"][str(sorted(map(int,contract["networks"].keys()))[0])]["address"] 149 150 avax_cchain_nonces = None 151 mm = None 152 153 def lock_nonce(agent): 154 global mm 155 # DECODE START 156 if not mm: 157 mm = mmap.mmap(avax_cchain_nonces.fileno(), 0) 158 159 mm.seek(0) 160 raw_data_cov = mm.read().decode('utf8') 161 nonce_data = json.loads(raw_data_cov) 162 163 nonce_data['locked'] = '1' 164 out_data = bytes(json.dumps(nonce_data), 'utf8') 165 mm[0:] = out_data 166 167 def get_nonce(agent): 168 global mm 169 # DECODE START 170 if not mm: 171 mm = mmap.mmap(avax_cchain_nonces.fileno(), 0) 172 173 mm.seek(0) 174 raw_data_cov = mm.read().decode('utf8') 175 nonce_data = json.loads(raw_data_cov) 176 current_block = int(w3.eth.get_block('latest')["number"]) 177 178 while nonce_data['locked'] == '1': 179 mm.seek(0) 180 raw_data_cov = mm.read().decode('utf8') 181 nonce_data = json.loads(raw_data_cov) 182 mm.seek(0) 183 continue 184 185 # locked == '1', unlocked == '0' 186 187 nonce_data[agent.address]["seen_block"] = decode_single('uint256', base64.b64decode(nonce_data[agent.address]["seen_block"])) 188 nonce_data[agent.address]["next_tx_count"] = decode_single('uint256', base64.b64decode(nonce_data[agent.address]["next_tx_count"])) 189 # DECODE END 190 191 if current_block != nonce_data[agent.address]["seen_block"]: 192 if (nonce_data[agent.address]["seen_block"] == 0): 193 nonce_data[agent.address]["seen_block"] = current_block 194 nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count 195 else: 196 nonce_data[agent.address]["seen_block"] = current_block 197 nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count 198 nonce_data[agent.address]["next_tx_count"] += 1 199 agent.next_tx_count = nonce_data[agent.address]["next_tx_count"] 200 else: 201 nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count 202 nonce_data[agent.address]["next_tx_count"] += 1 203 agent.next_tx_count = nonce_data[agent.address]["next_tx_count"] 204 205 # ENCODE START 206 nonce_data[agent.address]["seen_block"] = base64.b64encode(encode_single('uint256', nonce_data[agent.address]["seen_block"])).decode('ascii') 207 nonce_data[agent.address]["next_tx_count"] = base64.b64encode(encode_single('uint256', nonce_data[agent.address]["next_tx_count"])).decode('ascii') 208 209 # ENCODE END 210 return agent.next_tx_count 211 212 def unlock_nonce(agent): 213 global mm 214 # DECODE START 215 if not mm: 216 mm = mmap.mmap(avax_cchain_nonces.fileno(), 0) 217 218 mm.seek(0) 219 raw_data_cov = mm.read().decode('utf8') 220 nonce_data = json.loads(raw_data_cov) 221 222 nonce_data['locked'] = '0' 223 out_data = bytes(json.dumps(nonce_data), 'utf8') 224 mm[:] = out_data 225 226 def transaction_helper(agent, prepped_function_call, gas): 227 tx_hash = None 228 nonce = get_nonce(agent) 229 while tx_hash is None: 230 try: 231 agent.next_tx_count = nonce 232 lock_nonce(agent) 233 tx_hash = prepped_function_call.transact({ 234 'chainId': 43112, 235 'nonce': nonce, 236 'from' : getattr(agent, 'address', agent), 237 'gas': gas, 238 'gasPrice': Web3.toWei(225, 'gwei'), 239 }) 240 unlock_nonce(agent) 241 except Exception as inst: 242 err_str = str(inst) 243 if 'nonce too low' in err_str: 244 # increment tx_hash 245 unlock_nonce(agent) 246 nonce +=1 247 elif 'replacement transaction underpriced' in err_str: 248 # increment tx_hash 249 unlock_nonce(agent) 250 nonce +=1 251 else: 252 unlock_nonce(agent) 253 nonce +=1 254 print(inst) 255 return tx_hash 256 257 def pretty(d, indent=0): 258 """ 259 Pretty-print a value. 260 """ 261 if not isinstance(d, dict) and not isinstance(d, list): 262 print('\t' * (indent+1) + str(d)) 263 else: 264 for key, value in d.items(): 265 print('\t' * indent + str(key)) 266 if isinstance(value, dict): 267 pretty(value, indent+1) 268 elif isinstance(value, list): 269 for v in value: 270 pretty(v, indent+1) 271 else: 272 print('\t' * (indent+1) + str(value)) 273 274 def portion_dedusted(total, fraction): 275 """ 276 Compute the amount of an asset to use, given that you have 277 total and you don't want to leave behind dust. 278 """ 279 280 if total - (fraction * total) <= 1: 281 return total 282 else: 283 return fraction * total 284 285 def defaultdict_from_dict(d): 286 #nd = lambda: collections.defaultdict(nd) 287 ni = collections.defaultdict(set) 288 ni.update(d) 289 return ni 290 291 def execute_cmd(cmd): 292 try: 293 proc = Popen(cmd, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) 294 proc_data = proc.communicate()[0] 295 return proc_data 296 297 except Exception as inst: 298 print (inst) 299 data = inst 300 return data 301 302 # Because token balances need to be accuaate to the atomic unit, we can't store 303 # them as floats. Otherwise we might turn our float back into a token balance 304 # different from the balance we actually had, and try to spend more than we 305 # have. But also, it's ugly to throw around total counts of atomic units. So we 306 # use this class that represents a fixed-point token balance. 307 class Balance: 308 def __init__(self, wei=0, decimals=0): 309 self._wei = int(wei) 310 self._decimals = int(decimals) 311 312 def clone(self): 313 """ 314 Make a deep copy so += and -= on us won't infect the copy. 315 """ 316 return Balance(self._wei, self._decimals) 317 318 def to_decimals(self, new_decimals): 319 """ 320 Get a similar balance with a different number of decimals. 321 """ 322 323 return Balance(self._wei * 10**new_decimals // 10**self._decimals, new_decimals) 324 325 @classmethod 326 def from_tokens(cls, n, decimals=0): 327 return cls(n * 10**decimals, decimals) 328 329 def __add__(self, other): 330 if isinstance(other, Balance): 331 if other._decimals != self._decimals: 332 raise ValueError("Cannot add balances with different decimals: {}, {}", self, other) 333 return Balance(self._wei + other._wei, self._decimals) 334 else: 335 return Balance(self._wei + other * 10**self._decimals, self._decimals) 336 337 def __iadd__(self, other): 338 if isinstance(other, Balance): 339 if other._decimals != self._decimals: 340 raise ValueError("Cannot add balances with different decimals: {}, {}", self, other) 341 self._wei += other._wei 342 else: 343 self._wei += other * 10**self._decimals 344 return self 345 346 def __radd__(self, other): 347 return self + other 348 349 def __sub__(self, other): 350 if isinstance(other, Balance): 351 if other._decimals != self._decimals: 352 raise ValueError("Cannot subtract balances with different decimals: {}, {}", self, other) 353 return Balance(self._wei - other._wei, self._decimals) 354 else: 355 return Balance(self._wei - other * 10**self._decimals, self._decimals) 356 357 def __isub__(self, other): 358 if isinstance(other, Balance): 359 if other._decimals != self._decimals: 360 raise ValueError("Cannot subtract balances with different decimals: {}, {}", self, other) 361 self._wei -= other._wei 362 else: 363 self._wei -= other * 10**self._decimals 364 return self 365 366 def __rsub__(self, other): 367 return Balance(other * 10**self._decimals, self._decimals) - self 368 369 def __mul__(self, other): 370 if isinstance(other, Balance): 371 raise TypeError("Cannot multiply two balances") 372 return Balance(self._wei * other, self._decimals) 373 374 def __imul__(self, other): 375 if isinstance(other, Balance): 376 raise TypeError("Cannot multiply two balances") 377 self._wei = int(self._wei * other) 378 379 def __rmul__(self, other): 380 return self * other 381 382 def __truediv__(self, other): 383 if isinstance(other, Balance): 384 raise TypeError("Cannot divide two balances") 385 return Balance(self._wei // other, self._decimals) 386 387 def __itruediv__(self, other): 388 if isinstance(other, Balance): 389 raise TypeError("Cannot divide two balances") 390 self._wei = int(self._wei // other) 391 392 # No rtruediv because dividing by a balance is silly. 393 394 # Todo: floordiv? divmod? 395 396 def __lt__(self, other): 397 if isinstance(other, Balance): 398 if other._decimals != self._decimals: 399 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 400 return self._wei < other._wei 401 else: 402 return float(self) < other 403 404 def __le__(self, other): 405 if isinstance(other, Balance): 406 if other._decimals != self._decimals: 407 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 408 return self._wei <= other._wei 409 else: 410 return float(self) <= other 411 412 def __gt__(self, other): 413 if isinstance(other, Balance): 414 if other._decimals != self._decimals: 415 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 416 return self._wei > other._wei 417 else: 418 return float(self) > other 419 420 def __ge__(self, other): 421 if isinstance(other, Balance): 422 if other._decimals != self._decimals: 423 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 424 return self._wei >= other._wei 425 else: 426 return float(self) >= other 427 428 def __eq__(self, other): 429 if isinstance(other, Balance): 430 if other._decimals != self._decimals: 431 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 432 return self._wei == other._wei 433 else: 434 return float(self) == other 435 436 def __ne__(self, other): 437 if isinstance(other, Balance): 438 if other._decimals != self._decimals: 439 raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other) 440 return self._wei != other._wei 441 else: 442 return float(self) != other 443 444 def __str__(self): 445 base = 10**self._decimals 446 ipart = self._wei // base 447 fpart = self._wei - base * ipart 448 return ('{}.{:0' + str(self._decimals) + 'd}').format(ipart, fpart) 449 450 def __repr__(self): 451 return 'Balance({}, {})'.format(self._wei, self._decimals) 452 453 def __float__(self): 454 return self._wei / 10**self._decimals 455 456 def __round__(self): 457 return Balance(int(math.floor(self._wei / 10**self._decimals)) * 10**self._decimals, self._decimals) 458 459 def __format__(self, s): 460 if s == '': 461 return str(self) 462 return float(self).__format__(s) 463 464 def to_wei(self): 465 return self._wei 466 467 def decimals(self): 468 return self._decimals 469 470 class TokenProxy: 471 """ 472 A proxy for an ERC20 token. Monitors events, processes them when update() 473 is called, and fulfils balance requests from memory. 474 """ 475 476 def __init__(self, contract): 477 """ 478 Set up a proxy around a Web3py contract object that implements ERC20. 479 """ 480 481 self.__contract = contract 482 self.__transfer_filter = self.__contract.events.Transfer.createFilter(fromBlock='latest') 483 # This maps from string address to Balance balance 484 self.__balances = {} 485 # This records who we approved for who 486 self.__approved_file = "{}-{}.json".format(str(contract.address), 'approvals') 487 488 if not os.path.exists(self.__approved_file): 489 f = open(self.__approved_file, 'w+') 490 f.write('{}') 491 f.close() 492 tmp_file_data = {} 493 else: 494 data = open(self.__approved_file, 'r+').read() 495 tmp_file_data = {} if len(data) == 0 else json.loads(data) 496 self.__approved = tmp_file_data 497 498 # Load initial parameters from the chain. 499 # Assumes no events are happening to change the supply while we are doing this. 500 self.__decimals = self.__contract.functions.decimals().call() 501 self.__symbol = self.__contract.functions.symbol().call() 502 self.__supply = Balance(self.__contract.functions.totalSupply().call(), self.__decimals) 503 504 # Expose some properties to make us easy to use in place of the contract 505 506 @property 507 def decimals(self): 508 return self.__decimals 509 510 @property 511 def symbol(self): 512 return self.__symbol 513 514 @property 515 def totalSupply(self): 516 return self.__supply 517 518 @property 519 def address(self): 520 return self.__contract.address 521 522 @property 523 def contract(self): 524 return self.__contract 525 526 def update(self, is_init_agents=[]): 527 """ 528 Process pending events and update state to match chain. 529 Assumes no transactions are still in flight. 530 """ 531 532 # These addresses need to be polled because we have no balance from 533 # before all these events. 534 new_addresses = set() 535 try: 536 for transfer in self.__transfer_filter.get_new_entries(): 537 # For every transfer event since we last updated... 538 539 # Each loooks something like: 540 # AttributeDict({'args': AttributeDict({'from': '0x0000000000000000000000000000000000000000', 541 # 'to': '0x20042A784Bf0743fcD81136422e12297f52959a0', 'value': 19060347313}), 542 # 'event': 'Transfer', 'logIndex': 0, 'transactionIndex': 0, 543 # 'transactionHash': HexBytes('0xa6f4ca515b28301b224f24b7ee14b8911d783e2bf965dbcda5784b4296c84c23'), 544 # 'address': '0xa2Ff73731Ee46aBb6766087CE33216aee5a30d5e', 545 # 'blockHash': HexBytes('0xb5ffd135318581fcd5cd2463cf3eef8aaf238bef545e460c284ad6283928ed08'), 546 # 'blockNumber': 17}) 547 args = transfer['args'] 548 549 moved = Balance(args['value'], self.__decimals) 550 if args['from'] in self.__balances: 551 self.__balances[args['from']] -= moved 552 elif args['from'] == ZERO_ADDRESS: 553 # This is a mint 554 self.__supply += moved 555 else: 556 new_addresses.add(args['from']) 557 if args['to'] in self.__balances: 558 self.__balances[args['to']] += moved 559 elif args['to'] == ZERO_ADDRESS: 560 # This is a burn 561 self.__supply -= moved 562 else: 563 new_addresses.add(args['to']) 564 except: 565 pass 566 567 for address in new_addresses: 568 # TODO: can we get a return value and a correct-as-of block in the same call? 569 self.__balances[address] = Balance(self.__contract.functions.balanceOf(address).call(), self.__decimals) 570 571 if is_init_agents: 572 for agent in is_init_agents: 573 # TODO: can we get a return value and a correct-as-of block in the same call? 574 self.__balances[agent.address] = Balance(self.__contract.functions.balanceOf(agent.address).call(), self.__decimals) 575 576 577 def __getitem__(self, address): 578 """ 579 Get the balance of the given address as a Balance, with the given number of decimals. 580 581 Address can be a string or any object with an .address field. 582 """ 583 584 address = getattr(address, 'address', address) 585 586 if address not in self.__balances: 587 # Don't actually cache here; wait for a transfer. 588 # Transactions may still be in flight 589 return Balance(self.__contract.functions.balanceOf(address).call(), self.__decimals) 590 else: 591 # Clone the stored balance so it doesn't get modified and upset the user 592 return self.__balances[address].clone() 593 594 def ensure_approved(self, owner, spender): 595 """ 596 Approve the given spender to spend all the owner's tokens on their behalf. 597 598 Owner and spender may be addresses or things with addresses. 599 """ 600 spender = getattr(spender, 'address', spender) 601 602 if (getattr(owner, 'address', owner) not in self.__approved) or (spender not in self.__approved[getattr(owner, 'address', owner)]): 603 # Issue an approval 604 #logger.info('WAITING FOR APPROVAL {} for {}'.format(getattr(owner, 'address', owner), spender)) 605 tx_hash = transaction_helper( 606 owner, 607 self.__contract.functions.approve(spender, UINT256_MAX), 608 500000 609 ) 610 receipt = w3.eth.waitForTransactionReceipt(tx_hash, poll_latency=tx_pool_latency) 611 #logger.info('APPROVED') 612 if getattr(owner, 'address', owner) not in self.__approved: 613 self.__approved[getattr(owner, 'address', owner)] = {spender: 1} 614 else: 615 self.__approved[getattr(owner, 'address', owner)][spender] = 1 616 617 open(self.__approved_file, 'w+').write(json.dumps(self.__approved)) 618 619 def from_wei(self, wei): 620 """ 621 Convert a number of wei (possibly a float) into a Balance with the 622 right number of decimals. 623 """ 624 625 return Balance(wei, self.__decimals) 626 627 def from_tokens(self, tokens): 628 """ 629 Convert a number of token units (possibly a float) into a Balance with 630 the right number of decimals. 631 """ 632 633 return Balance.from_tokens(tokens, self.__decimals) 634 635 class Agent: 636 """ 637 Represents an agent. Tracks all the agent's balances. 638 """ 639 640 def __init__(self, linear_liquidity_pool, options_exchange, credit_provider, xsd_token, usdt_token, **kwargs): 641 642 # xSD TokenProxy 643 self.xsd_token = xsd_token 644 # USDT TokenProxy 645 self.usdt_token = usdt_token 646 647 self.option_tokens = [] 648 649 650 self.credit_provider = credit_provider 651 652 # avax balance 653 self.avax = kwargs.get("starting_avax", Balance(0, 18)) 654 655 # What's our max faith in the system in USDT? 656 self.max_faith = kwargs.get("max_faith", 0.0) 657 # And our min faith 658 self.min_faith = kwargs.get("min_faith", 0.0) 659 # Should we even use faith? 660 self.use_faith = kwargs.get("use_faith", True) 661 662 # add wallet addr 663 self.address = kwargs.get("wallet_address", '0x0000000000000000000000000000000000000000') 664 665 # Linear Liquidity Pool + Proxy 666 self.linear_liquidity_pool = linear_liquidity_pool 667 # Options Exchange 668 self.options_exchange = options_exchange 669 670 # keeps track of latest block seen for nonce tracking/tx 671 self.next_tx_count = w3.eth.getTransactionCount(self.address, block_identifier=int(w3.eth.get_block('latest')["number"])) 672 673 if kwargs.get("is_mint", False): 674 # need to mint USDT to the wallets for each agent 675 start_usdt_formatted = kwargs.get("starting_usdt", Balance(0, USDT["decimals"])) 676 tx_hash = transaction_helper( 677 self, 678 self.usdt_token.contract.functions.mint( 679 self.address, start_usdt_formatted.to_wei() 680 ), 681 500000 682 ) 683 time.sleep(1.1) 684 w3.eth.waitForTransactionReceipt(tx_hash, poll_latency=tx_pool_latency) 685 686 @property 687 def xsd(self): 688 """ 689 Get the current balance in USDT from the TokenProxy. 690 """ 691 return self.xsd_token[self] if self.xsd_token else 0 692 693 @property 694 def usdt(self): 695 """ 696 Get the current balance in USDT from the TokenProxy. 697 """ 698 return self.usdt_token[self] 699 700 @property 701 def total_written(self): 702 return Balance(self.options_exchange.get_total_owner_written(self), EXCHG['decimals']) 703 704 @property 705 def total_holding(self): 706 return Balance(self.options_exchange.get_total_owner_holding(self), EXCHG['decimals']) 707 708 @property 709 def lp(self): 710 """ 711 Get the current balance in Linear Liquidity Pool LP Shares from the TokenProxy. 712 """ 713 return self.linear_liquidity_pool[self] 714 715 @property 716 def short_collateral_exposure(self): 717 """ 718 Get the short collateral balance for agent 719 """ 720 return self.credit_provider.get_short_collateral_exposure(self) 721 722 def __str__(self): 723 """ 724 Turn into a readable string summary. 725 """ 726 return "Agent(xSD={:.2f}, usdt={:.2f}, avax={}, lp={}, total_written={}, total_holding={}, short_collateral_exposure={:.2f})".format( 727 self.xsd, self.usdt, self.avax, self.lp, self.total_written, self.total_holding, self.short_collateral_exposure) 728 729 730 def get_strategy(self, current_timestamp): 731 """ 732 Get weights, as a dict from action to float, as a function of the price. 733 """ 734 735 strategy = collections.defaultdict(lambda: 1.0) 736 737 strategy["buy"] = 1.0 738 strategy["sell"] = 10.0 739 strategy["write"] = 1.0 740 strategy["deposit_exchange"] = 1.0 741 strategy["deposit_pool"] = 1.0 742 strategy["add_symbol"] = 1.0 743 strategy["create_symbol"] = 1.0 744 745 746 if self.use_faith: 747 # Vary our strategy based on ... ? 748 pass 749 750 return strategy 751 752 def get_faith(self, current_timestamp, price, total_supply): 753 """ 754 Get the total faith in xSD that this agent has, in USDT. 755 756 If the market cap is over the faith, the agent thinks the system is 757 over-valued. If the market cap is under the faith, the agent thinks the 758 system is under-valued. 759 """ 760 761 # TODO: model the real economy as bidding on utility in 762 # mutually-beneficial exchanges conducted in xSD, for which a velocity 763 # is needed, instead of an abstract faith? 764 765 # TODO: different faith for different people 766 767 center_faith = (self.max_faith + self.min_faith) / 2 768 swing_faith = (self.max_faith - self.min_faith) / 2 769 faith = center_faith + swing_faith * math.sin(current_timestamp * (2 * math.pi / 5000000)) 770 771 return faith 772 773 class OptionsExchange: 774 def __init__(self, contract, usdt_token, btcusd_chainlink_feed, **kwargs): 775 self.contract = contract 776 self.usdt_token = usdt_token 777 self.btcusd_chainlink_feed = btcusd_chainlink_feed 778 self.option_tokens = {} 779 780 def balance(self, agent): 781 bal = self.contract.caller({'from' : agent.address, 'gas': 100000}).balanceOf(agent.address) 782 return Balance(bal, EXCHG['decimals']) 783 784 def resolve_token(self, agent, symbol): 785 ''' 786 resolveToken(symbol) 787 ''' 788 option_token_address = None 789 790 try: 791 option_token_address = self.contract.caller({'from' : agent.address, 'gas': 100000}).resolveToken(symbol) 792 793 794 except Exception as inst: 795 if "token not found" in str(inst): 796 pass 797 else: 798 raise(inst) 799 return option_token_address 800 801 def deposit_exchange(self, agent, amount): 802 ''' 803 ERC20 stablecoin = ERC20(0x123...); 804 OptionsExchange exchange = OptionsExchange(0xABC...); 805 806 address to = 0x456...; 807 uint value = 100e18; 808 stablecoin.approve(address(exchange), value); 809 exchange.depositTokens(to, address(stablecoin), value); 810 ''' 811 self.usdt_token.ensure_approved(agent, self.contract.address) 812 tx = transaction_helper( 813 agent, 814 self.contract.functions.depositTokens( 815 agent.address, 816 self.usdt_token.address, 817 amount.to_wei() 818 ), 819 500000 820 ) 821 return tx 822 823 def withdraw(self, agent, amount): 824 ''' 825 uint value = 50e18; 826 exchange.withdrawTokens(value); 827 ''' 828 tx = transaction_helper( 829 agent, 830 self.contract.functions.withdrawTokens( 831 amount.to_wei() 832 ), 833 8000000 834 ) 835 return tx 836 837 def calc_collateral(self, agent, feed_address, option_type, amount, strike_price, maturity): 838 ''' 839 840 address eth_usd_feed = address(0x987...); 841 uint volumeBase = 1e18; 842 uint strikePrice = 1300e18; 843 uint maturity = now + 30 days; 844 845 uint collateral = exchange.calcCollateral( 846 feed_address, 847 10 * volumeBase, 848 OptionsExchange.OptionType.CALL, 849 strikePrice, 850 maturity 851 ); 852 ''' 853 854 cc = self.contract.caller({'from' : agent.address, 'gas': 8000000}).calcCollateral( 855 feed_address, 856 Balance.from_tokens(amount, EXCHG['decimals']).to_wei(), 857 0 if option_type == 'CALL' else 1, 858 strike_price, 859 maturity 860 ) 861 return Balance(cc, EXCHG['decimals']) 862 863 def write(self, agent, feed_address, option_type, amount, strike_price, maturity): 864 ''' 865 address tkAddr = exchange.writeOptions( 866 eth_usd_feed, 867 10 * volumeBase, 868 OptionsExchange.OptionType.CALL, 869 strikePrice, 870 maturity, 871 holder 872 ); 873 ''' 874 tx = transaction_helper( 875 agent, 876 self.contract.functions.writeOptions( 877 feed_address, 878 Balance.from_tokens(amount, EXCHG['decimals']).to_wei(), 879 0 if option_type == 'CALL' else 1, 880 strike_price, 881 maturity, 882 agent.address 883 ), 884 8000000 885 ) 886 return tx 887 888 def create_symbol(self, agent, symbol, btcusd_chainlink_feed): 889 tx = transaction_helper( 890 agent, 891 self.contract.functions.createSymbol( 892 symbol, 893 btcusd_chainlink_feed.address 894 ), 895 8000000 896 ) 897 return tx 898 899 900 def burn_token(self, agent, option_token_address, token_amount): 901 ''' 902 uint amount = token_amount * volumeBase; 903 token.burn(amount); 904 ''' 905 option_token = w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address) 906 tx = transaction_helper( 907 agent, 908 option_token.functions.burn( 909 agent.address, 910 token_amount.to_wei() 911 ), 912 8000000 913 ) 914 return tx 915 916 def redeem_token(self, agent, option_token_address): 917 option_token = w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address) 918 tx = transaction_helper( 919 agent, 920 option_token.functions.redeem( 921 agent.address 922 ), 923 8000000 924 ) 925 return tx 926 927 def liquidate(self, agent, options_address, owner_address): 928 ''' 929 exchange.liquidateOptions() 930 ''' 931 tx = transaction_helper( 932 agent, 933 self.contract.functions.liquidateOptions( 934 options_address, 935 owner_address 936 ), 937 8000000 938 ) 939 return tx 940 941 def get_total_short_collateral_exposure(self, agent): 942 943 ''' 944 945 NEED TO FIGURE OUT A BETTER WAY TO TRACK THIS 946 return self.contract.caller({'from' : agent.address, 'gas': 100000}).getOptionsExchangeTotalExposure() 947 ''' 948 return 0 949 950 def get_total_written(self, agent): 951 ''' 952 - loop over all options tokens and get (for total written) 953 - totalWrittenVolume() 954 ''' 955 tw = 0 956 for k, ot in self.option_tokens.items(): 957 tw += ot.contract.caller({'from' : agent.address, 'gas': 100000}).totalWrittenVolume() 958 return tw 959 960 def get_total_holding(self, agent): 961 ''' 962 - loop over all options tokens and get (for total holding) 963 - totalSupply() 964 ''' 965 th = 0 966 for k, ot in self.option_tokens.items(): 967 th += ot.contract.caller({'from' : agent.address, 'gas': 100000}).totalSupply() 968 return th 969 970 def get_total_owner_written(self, agent): 971 ''' 972 - loop over all options tokens and get (for written) 973 - writtenVolume(address owner) 974 ''' 975 ow = 0 976 for k, ot in self.option_tokens.items(): 977 ow += ot.contract.caller({'from' : agent.address, 'gas': 8000000}).writtenVolume(agent.address) 978 return ow 979 980 def get_total_owner_holding(self, agent): 981 ''' 982 - loop over all options tokens and get (for holding) 983 - balanceOf(address owner) 984 ''' 985 oh = 0 986 for k, ot in self.option_tokens.items(): 987 oh += ot.contract.caller({'from' : agent.address, 'gas': 8000000}).balanceOf(agent.address) 988 return oh 989 990 def calc_collateral_surplus(self, checker, agent): 991 cs = self.contract.caller({'from' : checker.address, 'gas': 8000000}).calcSurplus(agent.address) 992 return Balance(cs, EXCHG['decimals']) 993 994 def prefetch_daily(self, agent, current_round_id, iv_bin_window): 995 ''' 996 997 First call prefetchDailyPrice passing in the "roundId" of the latest sample you appended to your mock, corresponding to the underlying price for the new day 998 Then call prefetchDailyVolatility passing in the volatility period defined in the ProtocolSettings contract (defaults to 90 days) 999 Maybe twap can be updated daily? 1000 ''' 1001 txr = transaction_helper( 1002 agent, 1003 self.btcusd_chainlink_feed.functions.prefetchDailyPrice( 1004 current_round_id 1005 ), 1006 8000000 1007 ) 1008 txr_recp = w3.eth.waitForTransactionReceipt(txr, poll_latency=tx_pool_latency, timeout=600) 1009 print("prefetchDailyPrice", txr_recp) 1010 1011 txv = transaction_helper( 1012 agent, 1013 self.btcusd_chainlink_feed.functions.prefetchDailyVolatility( 1014 iv_bin_window 1015 ), 1016 8000000 1017 ) 1018 txv_recp = w3.eth.waitForTransactionReceipt(txv, poll_latency=tx_pool_latency, timeout=600) 1019 print("prefetchDailyVolatility", txr_recp) 1020 1021 def prefetch_sample(self, agent): 1022 txr = transaction_helper( 1023 agent, 1024 self.btcusd_chainlink_feed.functions.prefetchSample(), 1025 8000000 1026 ) 1027 txv_recp = w3.eth.waitForTransactionReceipt(txr, poll_latency=tx_pool_latency, timeout=600) 1028 1029 class CreditProvider: 1030 def __init__(self, contract, **kwargs): 1031 self.contract = contract 1032 1033 def get_total_balance(self, agent): 1034 ''' 1035 Get total balance of stable coins assinged to agents 1036 ''' 1037 return self.contract.caller({'from' : agent.address, 'gas': 100000}).getTotalBalance() 1038 1039 def get_token_stock(self, agent): 1040 ''' 1041 Get total balance erc20 stables coins deposited into credit provider 1042 ''' 1043 return self.contract.caller({'from' : agent.address, 'gas': 100000}).totalTokenStock() 1044 1045 def get_total_debt(self, agent): 1046 return Balance(self.contract.caller({'from' : agent.address, 'gas': 8000000}).totalDebt(), EXCHG['decimals']) 1047 1048 def get_short_collateral_exposure(self, agent): 1049 return Balance(self.contract.caller({'from' : agent.address, 'gas': 8000000}).calcRawCollateralShortage(agent.address), EXCHG['decimals']) 1050 1051 class LinearLiquidityPool(TokenProxy): 1052 def __init__(self, contract, usdt_token, options_exchange, **kwargs): 1053 self.usdt_token = usdt_token 1054 self.options_exchange = options_exchange 1055 super().__init__(contract) 1056 1057 def deposit_pool(self, agent, amount): 1058 ''' 1059 pool.depositTokens( 1060 address to, address token, uint value 1061 ); 1062 ''' 1063 self.usdt_token.ensure_approved(agent, self.contract.address) 1064 tx = transaction_helper( 1065 agent, 1066 self.contract.functions.depositTokens( 1067 agent.address, 1068 self.usdt_token.address, 1069 amount.to_wei() 1070 ), 1071 500000 1072 ) 1073 return tx 1074 1075 def redeem_pool(self, agent): 1076 ''' 1077 pool.redeem(address) 1078 ''' 1079 tx = transaction_helper( 1080 agent, 1081 self.contract.functions.redeem( 1082 agent.address 1083 ), 1084 8000000 1085 ) 1086 return tx 1087 1088 def query_buy(self, agent, symbol): 1089 print(symbol) 1090 price_volume = self.contract.caller({'from' : agent.address, 'gas': 80000000}).queryBuy(symbol) 1091 return price_volume 1092 1093 def buy(self, agent, symbol, price, volume): 1094 ''' 1095 stablecoin.approve(address(pool), price * volume / volumeBase); 1096 pool.buy(symbol, price, volume, address(stablecoin)); 1097 ''' 1098 self.usdt_token.ensure_approved(agent, self.contract.address) 1099 tx = transaction_helper( 1100 agent, 1101 self.contract.functions.buy( 1102 symbol, 1103 price, 1104 volume.to_wei(), 1105 self.usdt_token.contract.address 1106 ), 1107 8000000 1108 ) 1109 return tx 1110 1111 def query_sell(self, agent, symbol): 1112 price_volume = self.contract.caller({'from' : agent.address, 'gas': 80000000}).querySell(symbol) 1113 return price_volume 1114 1115 def sell(self, agent, symbol, price, volume, option_token): 1116 ''' 1117 option_token.approve(address(pool), price * volume / volumeBase)`; 1118 pool.sell(symbol, price, volume, 0, 0)`; 1119 ''' 1120 ''' 1121 txc = transaction_helper( 1122 agent, 1123 self.options_exchange.contract.functions.setCollateral( 1124 self.contract.address 1125 ), 1126 8000000 1127 ) 1128 w3.eth.waitForTransactionReceipt(txc, poll_latency=tx_pool_latency) 1129 ''' 1130 1131 option_token.ensure_approved(agent, self.contract.address) 1132 tx = transaction_helper( 1133 agent, 1134 self.contract.functions.sell( 1135 symbol, 1136 price, 1137 volume.to_wei() 1138 ), 1139 8000000 1140 ) 1141 return tx 1142 1143 ''' 1144 EXTRACT TIMESTAMP FROM SYMBOL AND FILTER ON IF BEFORE OR AFTER CURRENT BLOCK TIMESTAMP 1145 ''' 1146 def list_symbols(self, agent): 1147 symbols = self.contract.caller({'from' : agent.address, 'gas': 8000000}).listSymbols().split('\n') 1148 return [x for x in list(filter(None,symbols)) if x != ''] 1149 1150 ''' 1151 EXTRACT TIMESTAMP FROM SYMBOL AND FILTER ON IF BEFORE OR AFTER CURRENT BLOCK TIMESTAMP 1152 ''' 1153 def list_expired_symbols(self, agent): 1154 symbols = self.contract.caller({'from' : agent.address, 'gas': 8000000}).listExpiredSymbols().split('\n') 1155 return [x for x in list(filter(None,symbols)) if x != ''] 1156 1157 def get_option_tokens(self, agent): 1158 symbols = self.list_symbols(agent) 1159 option_tokens = [] 1160 for sym in symbols: 1161 option_token_address = self.options_exchange.resolve_token(agent, sym) 1162 if option_token_address: 1163 option_token = TokenProxy(w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address)) 1164 option_tokens.append(option_token) 1165 1166 return option_tokens 1167 1168 def get_option_tokens_expired(self, agent): 1169 symbols = self.list_expired_symbols(agent) 1170 option_tokens = [] 1171 for sym in symbols: 1172 option_token_address = self.options_exchange.resolve_token(agent, sym) 1173 if option_token_address: 1174 option_token = TokenProxy(w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address)) 1175 option_tokens.append(option_token) 1176 1177 return option_tokens 1178 1179 def add_symbol(self, agent, udlfeed_address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock): 1180 ''' 1181 pool.addSymbol( 1182 address(feed), 1183 strike, 1184 maturity, 1185 CALL, 1186 time.getNow(), 1187 time.getNow() + 1 days, 1188 x, 1189 y, 1190 100 * volumeBase, // buy stock 1191 200 * volumeBase // sell stock 1192 ); 1193 ''' 1194 tx = transaction_helper( 1195 agent, 1196 self.contract.functions.addSymbol( 1197 udlfeed_address, 1198 strike * (10**EXCHG['decimals']), 1199 maturity, 1200 0 if option_type == 'CALL' else 1, 1201 current_timestamp, 1202 current_timestamp + (60 * 60 * 24 * 2), 1203 x, 1204 y, 1205 buyStock * 10**EXCHG['decimals'], 1206 sellStock * 10**EXCHG['decimals'] 1207 ), 1208 8000000 1209 ) 1210 return tx 1211 1212 def update_symbol(self, agent, udlfeed_address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock): 1213 ''' 1214 pool.addSymbol( 1215 address(feed), 1216 strike, 1217 maturity, 1218 CALL, 1219 time.getNow(), 1220 time.getNow() + 1 days, 1221 x, 1222 y, 1223 100 * volumeBase, // buy stock 1224 200 * volumeBase // sell stock 1225 ); 1226 ''' 1227 tx = transaction_helper( 1228 agent, 1229 self.contract.functions.addSymbol( 1230 udlfeed_address, 1231 strike * (10**EXCHG['decimals']), 1232 maturity, 1233 0 if option_type == 'CALL' else 1, 1234 current_timestamp, 1235 current_timestamp + (60 * 60 * 24 * 2), 1236 x, 1237 y, 1238 buyStock * (10**EXCHG['decimals']), 1239 sellStock * (10**EXCHG['decimals']) 1240 ), 1241 8000000 1242 ) 1243 return tx 1244 1245 def pool_free_balance(self, agent): 1246 pool_free_balance = self.contract.caller({'from' : agent.address, 'gas': 100000}).calcFreeBalance() 1247 return Balance(pool_free_balance, EXCHG['decimals']) 1248 1249 class Model: 1250 """ 1251 Full model of the economy. 1252 """ 1253 1254 def __init__(self, options_exchange, credit_provider, linear_liquidity_pool, btcusd_chainlink_feed, btcusd_agg, btcusd_data, xsd, usdt, agents, **kwargs): 1255 """ 1256 Takes in experiment parameters and forwards them on to all components. 1257 """ 1258 1259 self.agents = [] 1260 self.options_exchange = OptionsExchange(options_exchange, usdt, btcusd_chainlink_feed, **kwargs) 1261 self.credit_provider = CreditProvider(credit_provider, **kwargs) 1262 self.linear_liquidity_pool = LinearLiquidityPool(linear_liquidity_pool, usdt, self.options_exchange, **kwargs) 1263 self.btcusd_chainlink_feed = btcusd_chainlink_feed 1264 self.btcusd_agg = btcusd_agg 1265 self.btcusd_data = btcusd_data 1266 self.btcusd_data_init_bins = 30 1267 self.current_round_id = 30 1268 self.daily_vol_period = 30 1269 self.prev_timestamp = 0 1270 self.daily_period = 60 * 60 * 24 1271 self.weekly_period = self.daily_period * 7 1272 self.days_per_year = 365 1273 self.months_per_year = 12 1274 self.option_tokens = {} 1275 self.option_tokens_expired = {} 1276 self.option_tokens_expired_to_burn = {} 1277 self.usdt_token = usdt 1278 self.symbol_created = {} 1279 1280 is_mint = is_try_model_mine 1281 if w3.eth.get_block('latest')["number"] == block_offset: 1282 # THIS ONLY NEEDS TO BE RUN ON NEW CONTRACTS 1283 # TODO: tolerate redeployment or time-based generation 1284 is_mint = True 1285 1286 total_tx_submitted = len(agents) 1287 for i in range(len(agents)): 1288 1289 address = agents[i] 1290 agent = Agent(self.linear_liquidity_pool, self.options_exchange, self.credit_provider, xsd, usdt, starting_axax=0, starting_usdt=0, wallet_address=address, is_mint=is_mint, **kwargs) 1291 1292 self.agents.append(agent) 1293 1294 1295 is_print_agent_state = False 1296 1297 if is_print_agent_state: 1298 # Update caches to current chain state 1299 self.usdt_token.update(is_init_agents=self.agents) 1300 self.linear_liquidity_pool.update(is_init_agents=self.agents) 1301 1302 for x in self.linear_liquidity_pool.get_option_tokens(self.agents[0]): 1303 if x.address not in self.option_tokens: 1304 self.option_tokens[x.address] = x 1305 1306 self.options_exchange.option_tokens = self.option_tokens 1307 1308 1309 for i in range(len(agents)): 1310 logger.info(self.agents[i]) 1311 1312 sys.exit() 1313 1314 1315 ''' 1316 INIT T-MINUS DATA FOR FEED 1317 ''' 1318 1319 if self.prev_timestamp == 0: 1320 current_timestamp = w3.eth.get_block('latest')['timestamp'] 1321 seleted_advancer = self.agents[0] 1322 transaction_helper( 1323 seleted_advancer, 1324 self.btcusd_agg.functions.setRoundIds( 1325 range(30) 1326 ), 1327 500000 1328 ) 1329 1330 transaction_helper( 1331 seleted_advancer, 1332 self.btcusd_agg.functions.setAnswers( 1333 self.btcusd_data[:self.btcusd_data_init_bins] 1334 ), 1335 500000 1336 ) 1337 1338 timestamps = [(x* self.daily_period * -1) + current_timestamp for x in range(self.btcusd_data_init_bins, 0, -1)] 1339 transaction_helper( 1340 seleted_advancer, 1341 self.btcusd_agg.functions.setUpdatedAts( 1342 timestamps 1343 ), 1344 500000 1345 ) 1346 1347 print(timestamps) 1348 print(self.btcusd_data[:self.btcusd_data_init_bins]) 1349 1350 tx = transaction_helper( 1351 seleted_advancer, 1352 self.btcusd_chainlink_feed.functions.initialize( 1353 timestamps, 1354 self.btcusd_data[:self.btcusd_data_init_bins] 1355 ), 1356 8000000 1357 ) 1358 receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600) 1359 print(receipt) 1360 1361 1362 def log(self, stream, seleted_advancer, header=False): 1363 """ 1364 Log model statistics a TSV line. 1365 If header is True, include a header. 1366 """ 1367 1368 if header: 1369 stream.write("#block\twritten\tholding\texposure\ttotal CB\ttotal SB\ttotal debt\n")#\tfaith\n") 1370 1371 print( 1372 w3.eth.get_block('latest')["number"], 1373 self.options_exchange.get_total_written(seleted_advancer), 1374 self.options_exchange.get_total_holding(seleted_advancer), 1375 self.options_exchange.get_total_short_collateral_exposure(seleted_advancer), 1376 self.credit_provider.get_total_balance(seleted_advancer), 1377 self.credit_provider.get_token_stock(seleted_advancer), 1378 self.credit_provider.get_total_debt(seleted_advancer) 1379 ) 1380 1381 stream.write('{}\t{}\t{}\t{:.2f}\t{:.2f}\t{:.2f}\t{:.2f}\n'.format( 1382 w3.eth.get_block('latest')["number"], 1383 self.options_exchange.get_total_written(seleted_advancer), 1384 self.options_exchange.get_total_holding(seleted_advancer), 1385 self.options_exchange.get_total_short_collateral_exposure(seleted_advancer), 1386 self.credit_provider.get_total_balance(seleted_advancer), 1387 self.credit_provider.get_token_stock(seleted_advancer), 1388 self.credit_provider.get_total_debt(seleted_advancer) 1389 ) 1390 ) 1391 1392 def get_overall_faith(self): 1393 """ 1394 Probably should be related to credit token? 1395 """ 1396 pass 1397 1398 def is_positve_option_token_expired_balance(self, agent): 1399 tokens = [] 1400 for k,v in self.option_tokens_expired.items(): 1401 if v[agent] > 0: 1402 tokens.append(v) 1403 return False 1404 1405 def is_positive_option_token_balance(self, agent): 1406 tokens = [] 1407 for k,v in self.option_tokens.items(): 1408 if v[agent] > 0: 1409 tokens.append(v) 1410 1411 for k,v in self.option_tokens_expired.items(): 1412 if v[agent] > 0: 1413 tokens.append(v) 1414 return False 1415 1416 def step(self): 1417 """ 1418 Step the model Let all the agents act. 1419 1420 Returns True if anyone could act. 1421 """ 1422 # Update caches to current chain state for all the tokens 1423 self.usdt_token.update() 1424 self.linear_liquidity_pool.update() 1425 1426 current_timestamp = w3.eth.get_block('latest')['timestamp'] 1427 diff_timestamp = current_timestamp - self.prev_timestamp 1428 1429 ''' 1430 TODO: 1431 randomly have an agent do maintence tasks the epoch, in order to simulate people using governance tokens to do these task, for now, use initializing agent 1432 ''' 1433 random_advancer = self.agents[int(random.random() * (len(self.agents) - 1))] 1434 seleted_advancer = self.agents[0] 1435 1436 available_symbols = self.linear_liquidity_pool.list_symbols(seleted_advancer) 1437 1438 1439 ''' 1440 TODO: need to explore 2:1 bs, 1:1 bs and 1:2 bs 1441 ''' 1442 1443 buyStock = 100000 1444 sellStock = 100000 1445 1446 1447 for x in self.linear_liquidity_pool.get_option_tokens(random_advancer): 1448 if x.address not in self.option_tokens: 1449 self.option_tokens[x.address] = x 1450 1451 for x in self.linear_liquidity_pool.get_option_tokens_expired(random_advancer): 1452 if x.totalSupply >= 1: 1453 self.option_tokens_expired[x.address] = x 1454 1455 else: 1456 self.option_tokens_expired_to_burn[x.address] = x 1457 1458 # need to remove expired tokens from reset for long running sims 1459 if x.address in self.option_tokens: 1460 del self.option_tokens[x.address] 1461 1462 self.options_exchange.option_tokens = self.option_tokens 1463 1464 ''' 1465 UPDATE FEEDS WHEN LASTEST DAY PASSESS 1466 UPDATE SYMBOL PARAMS 1467 ''' 1468 1469 #if (datetime.datetime.fromtimestamp(current_timestamp, tz=datetime.timezone.utc).date() > datetime.datetime.fromtimestamp(self.prev_timestamp, tz=datetime.timezone.utc).date()): 1470 if (datetime.datetime.fromtimestamp(current_timestamp).date() > datetime.datetime.fromtimestamp(self.prev_timestamp).date()): 1471 1472 if self.prev_timestamp > 0: 1473 # increment round id 1474 self.current_round_id += 1 1475 1476 tx = transaction_helper( 1477 seleted_advancer, 1478 self.btcusd_agg.functions.appendRoundId( 1479 self.current_round_id 1480 ), 1481 500000 1482 ) 1483 receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600) 1484 print("appendRoundId:", receipt) 1485 1486 tx = transaction_helper( 1487 seleted_advancer, 1488 self.btcusd_agg.functions.appendAnswer( 1489 self.btcusd_data[self.current_round_id] 1490 ), 1491 500000 1492 ) 1493 receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600) 1494 print("appendAnswer:", receipt) 1495 1496 tx = transaction_helper( 1497 seleted_advancer, 1498 self.btcusd_agg.functions.appendUpdatedAt( 1499 current_timestamp 1500 ), 1501 500000 1502 ) 1503 receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600) 1504 print("appendUpdatedAt:", receipt) 1505 1506 self.options_exchange.prefetch_daily(seleted_advancer, self.current_round_id, self.daily_vol_period * self.daily_period) 1507 1508 1509 ''' 1510 Prepare JSON data 1511 1512 { 1513 "BTC/USD": { 1514 "vol": 0.04, 1515 "data": [ 1516 {"strike": 1234, "option_type": "PUT", vol} 1517 ], 1518 } 1519 } 1520 ''' 1521 maturity = None 1522 with open('mcmc_symbol_params.json', 'w+') as f: 1523 mcmc_data = {} 1524 for sym in available_symbols: 1525 print('update symbol param file:', sym) 1526 sym_parts = sym.split('-') 1527 1528 strike = int(float(sym_parts[2]) / 10**EXCHG['decimals']) 1529 maturity = int(sym_parts[3]) 1530 1531 option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL' 1532 current_price = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals']) 1533 1534 if sym_parts[0] in mcmc_data: 1535 # just append new strike data 1536 mcmc_data[sym_parts[0]]["data"].append({ 1537 "strike": strike, 1538 "option_type": option_type, 1539 "symbol": sym 1540 }) 1541 else: 1542 # need to calc feed vol 1543 # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING VOL 1544 try: 1545 vol = self.btcusd_chainlink_feed.caller({'from' : random_advancer.address, 'gas': 8000000}).getDailyVolatility( 1546 self.daily_vol_period * self.daily_period 1547 ) 1548 except Exception as inst: 1549 print(inst, "bad vol calc") 1550 continue 1551 1552 multiplier = 3.0 1553 tvol = (vol / (10.**EXCHG['decimals'])) 1554 normed_vol = math.log((current_price + (tvol * multiplier)) / (current_price - (tvol * multiplier))) 1555 1556 mcmc_data[sym_parts[0]] = {} 1557 mcmc_data[sym_parts[0]]["curr_price"] = current_price 1558 mcmc_data[sym_parts[0]]["vol"] = normed_vol 1559 mcmc_data[sym_parts[0]]["data"] = [{ 1560 "strike": strike, 1561 "option_type": option_type, 1562 "symbol": sym 1563 }] 1564 1565 f.write(json.dumps(mcmc_data, indent=4)) 1566 1567 1568 1569 1570 if maturity != None: 1571 ''' 1572 EXECUTE MODEL HERE FOR ALL DATA 1573 EXAMPLE: ./op_model "2000" "0.2" "3.0" 1574 ''' 1575 days_until_expiry = (maturity - current_timestamp) / self.daily_period 1576 months_to_exp = days_until_expiry / (self.days_per_year / 12.0) 1577 num_samples = 2000 1578 1579 cmd = './op_model "%s" "%s" "%s"' % ( 1580 num_samples, 1581 0.05, 1582 months_to_exp, 1583 ) 1584 model_ret = str(execute_cmd(cmd)) 1585 1586 1587 ''' 1588 LOAD IN FILE AND MAP DATA TO PAIR 1589 ''' 1590 with open('mcmc_symbol_computation.json', 'r+') as f: 1591 mcmc_symbol_computation = json.loads(f.read()) 1592 if mcmc_symbol_computation: 1593 for sym in available_symbols: 1594 print('update symbol:', sym) 1595 sym_parts = sym.split('-') 1596 1597 if (sym in mcmc_symbol_computation) and mcmc_symbol_computation[sym]: 1598 x0s = mcmc_symbol_computation[sym]['x'] 1599 if len(x0s) == 0: 1600 continue 1601 1602 try: 1603 x = [Balance.from_tokens(round(x0,4), EXCHG['decimals']).to_wei() for x0 in x0s] 1604 y = mcmc_symbol_computation[sym]['y0'] + mcmc_symbol_computation[sym]['y1'] 1605 y = [Balance.from_tokens(round(y0,4), EXCHG['decimals']).to_wei() for y0 in y] 1606 print(x) 1607 print(y) 1608 except Exception as inst: 1609 print(inst, "no timestamp data to update") 1610 x = [0, 0] 1611 y = [0, 0, 0, 0] 1612 1613 strike = int(float(sym_parts[2]) / 10**EXCHG['decimals']) 1614 option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL' 1615 current_timestamp = w3.eth.get_block('latest')['timestamp'] 1616 sym_upd8_tx = self.linear_liquidity_pool.update_symbol(seleted_advancer, self.btcusd_chainlink_feed.address, strike, int(sym_parts[3]), option_type, current_timestamp, x, y, buyStock, sellStock) 1617 receipt = w3.eth.waitForTransactionReceipt(sym_upd8_tx, poll_latency=tx_pool_latency, timeout=600) 1618 print('update hash:', receipt) 1619 1620 1621 ''' 1622 TODO: NEED TO DUMP TO FILE AND LOAD FROM FILE IN ORDER TO BE ABLE TO START AND STOP MORE ROBUSTLY SANS NUKING CONTRACTS 1623 ''' 1624 self.prev_timestamp = current_timestamp 1625 1626 print("current prev_timestamp daily:", self.prev_timestamp) 1627 1628 1629 logger.info("Clock: {}".format(current_timestamp)) 1630 logger.info("current_round_id: {}".format(self.current_round_id)) 1631 1632 shuffled_agents = list(range(len(self.agents))) 1633 random.shuffle(shuffled_agents) 1634 1635 # return list of addres for agents who are short collateral 1636 any_short_collateral = [] 1637 while True: 1638 try: 1639 any_short_collateral = [a for a in self.agents if self.options_exchange.calc_collateral_surplus(random_advancer, a) <= 0] 1640 break 1641 except Exception as inst: 1642 print(inst, 'trying to pretetch sample') 1643 self.options_exchange.prefetch_sample(random_advancer) 1644 break 1645 1646 1647 tx_hashes = [] 1648 total_tx_submitted = 0 1649 1650 unique_available_symbols = list(set([asym.split('-')[1] for asym in available_symbols])) 1651 tks_list_symbols = [tv.symbol for tv in list(self.option_tokens.values())] 1652 1653 pool_free_balance = self.linear_liquidity_pool.pool_free_balance(random_advancer) 1654 logger.info("pool_free_balance: {}".format(pool_free_balance)) 1655 1656 any_calls = any([ts for ts in available_symbols if '-EC-' in ts]) 1657 any_puts = any([ts for ts in available_symbols if '-EP-' in ts]) 1658 1659 print("available_symbols:", available_symbols, len(available_symbols), len(tks_list_symbols)) 1660 print((not any_calls or not any_puts)) 1661 1662 self.has_tried_liquidating = False 1663 1664 max_symbols_per_type = 10 1665 1666 for agent_num in shuffled_agents: 1667 # TODO: real strategy 1668 a = self.agents[agent_num] 1669 options = [] 1670 1671 open_option_tokens = self.is_positive_option_token_balance(a) 1672 open_option_expired_tokens = self.is_positve_option_token_expired_balance(a) 1673 1674 exchange_bal = self.options_exchange.balance(a) 1675 pool_free_balance = self.linear_liquidity_pool.pool_free_balance(a) 1676 exchange_free_bal = Balance.from_tokens(0, EXCHG['decimals']) 1677 try: 1678 exchange_free_bal = self.options_exchange.calc_collateral_surplus(a, a) 1679 except Exception as inst: 1680 pass 1681 1682 1683 #''' 1684 #TODO: NEED A BETTER WAY TO FIGURE THIS OUT FOR SYMBOLS THAT FAIL TO UPDATE/HAVE ANY POS TXs 1685 available_symbols = self.linear_liquidity_pool.list_symbols(seleted_advancer) 1686 any_calls = [ts for ts in available_symbols if '-EC-' in ts] 1687 any_puts = [ts for ts in available_symbols if '-EP-' in ts] 1688 1689 if len(available_symbols) != len(self.option_tokens): 1690 #TRY AND UPDATE OPTION TOKENS AVAILABLE 1691 for x in self.linear_liquidity_pool.get_option_tokens(seleted_advancer): 1692 if x.address not in self.option_tokens: 1693 self.option_tokens[x.address] = x 1694 1695 tks_list_symbols = [tv.symbol for tv in list(self.option_tokens.values())] 1696 self.options_exchange.option_tokens = self.option_tokens 1697 #''' 1698 1699 if exchange_bal > 0 and len(available_symbols) > 0 and (pool_free_balance > exchange_bal): 1700 options.append('write') 1701 1702 if (exchange_bal > 0 or pool_free_balance > 0) and ((len(any_calls) < max_symbols_per_type or len(any_puts) < max_symbols_per_type) or (len(available_symbols) < max_symbols_per_type*2)): 1703 options.append('add_symbol') 1704 1705 if len(available_symbols) != len(tks_list_symbols): 1706 options.append('create_symbol') 1707 1708 1709 ''' 1710 if (exchange_bal > 0) and len(self.option_tokens) > 0 and (a.total_written <= a.total_holding): 1711 options.append('buy') 1712 ''' 1713 1714 if (a.total_holding > 1 or a.total_written > 1) and (pool_free_balance > 0): 1715 options.append('sell') 1716 1717 if (a.usdt > 0 and a.total_written < 1 and len(available_symbols) != len(self.option_tokens)) or (len(available_symbols) < max_symbols_per_type*2): 1718 options.append('deposit_exchange') 1719 1720 #len(available_symbols) != len(self.option_tokens) is to limit deposits to pool 1721 if (a.usdt > 0 and a.total_written < 1 and a.total_holding < 1 and len(available_symbols) != len(self.option_tokens)) or (len(available_symbols) < max_symbols_per_type*2): 1722 options.append('deposit_pool') 1723 1724 if exchange_free_bal > 0: 1725 options.append('withdraw') 1726 1727 ''' 1728 TODO: 1729 can only redeem after pool expires 1730 if a.lp > 0: 1731 options.append('redeem_pool') 1732 ''' 1733 1734 # option position must be short collateral 1735 ''' 1736 TODO: 1737 Test later, since only book positions are pretty much have the pool as the owner for options issued 1738 ''' 1739 if len(any_short_collateral) > 0 and not self.has_tried_liquidating: 1740 options.append('liquidate') 1741 1742 # liquidate expired positons 1743 if (len(any_short_collateral) > 0) or open_option_expired_tokens or (len(self.option_tokens_expired) > 0): 1744 options.append('liquidate_self') 1745 options.append('redeem_token') 1746 1747 #''' 1748 if len(self.option_tokens_expired_to_burn) > 0 or len(self.option_tokens) > 0: 1749 options.append('burn_token') 1750 #''' 1751 1752 1753 start_tx_count = a.next_tx_count 1754 commitment = random.random() * 0.01 1755 1756 if len(options) > 0: 1757 # We can act 1758 1759 ''' 1760 LATER: 1761 advance: to do maintainence functions, payout from dynamic collateral and/or gov token 1762 1763 TODO: 1764 burn_pool? 1765 1766 TOTEST: 1767 burn_pool? 1768 1769 TOTESTLATER: 1770 burn_token (may not be needed, handled by liquidation of option token), redeem_pool (can only reedeem after expiration) 1771 WORKS: 1772 deposit_exchange, deposit_pool, add_symbol, update_symbol, write, create_symbol, buy, liquidate_self, liquidate, withdraw, sell, redeem_token 1773 1774 ''' 1775 1776 strategy = a.get_strategy(w3.eth.get_block('latest')["number"]) 1777 1778 weights = [strategy[o] for o in options] 1779 1780 action = random.choices(options, weights=weights)[0] 1781 1782 # What fraction of the total possible amount of doing this 1783 # action will the agent do? 1784 1785 1786 if action == "deposit_exchange": 1787 amount = portion_dedusted( 1788 a.usdt, 1789 commitment * 0.1 1790 ) 1791 try: 1792 dpe_hash = self.options_exchange.deposit_exchange(a, amount) 1793 tx_hashes.append({'type': 'deposit_exchange', 'hash': dpe_hash}) 1794 except Exception as inst: 1795 logger.info({"agent": a.address, "error": inst, "action": "deposit_exchange", "amount": amount}) 1796 elif action == "add_symbol": 1797 option_types = ['PUT', 'CALL'] 1798 # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING STRIKES 1799 current_price = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals']) 1800 1801 """ 1802 TODO: 1803 choose random maturity length less than the maturity of the pool? 1 month for now 1804 """ 1805 current_timestamp = w3.eth.get_block('latest')['timestamp'] 1806 num_months = 6.0 # 1.0 1807 maturity = int(current_timestamp + (self.daily_period * (self.days_per_year / self.months_per_year * num_months))) 1808 days_until_expiry = (maturity - current_timestamp) / self.daily_period 1809 months_to_exp = days_until_expiry / (self.days_per_year / 12.0) 1810 num_samples = 2000 1811 option_type = option_types[0 if random.random() > 0.5 else 1] 1812 1813 is_otm_only = False 1814 1815 moneyness = max(0.0, random.random()) 1816 1817 if not is_otm_only: 1818 if random.random() > 0.5: 1819 strike = round(current_price * (1 + moneyness)) 1820 else: 1821 strike = round(current_price * (1 - moneyness)) 1822 1823 else: 1824 if option_type == 'CALL': 1825 # if call, write OTM by random amout, to the upside 1826 strike = round(current_price * (1 + moneyness)) 1827 else: 1828 # if put, write OTM by random amount, to the downside 1829 strike = round(current_price * (1 - moneyness)) 1830 1831 with open('mcmc_symbol_params.json', 'w+') as f: 1832 mcmc_data = {} 1833 # need to calc feed vol 1834 # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING VOL 1835 try: 1836 vol = self.btcusd_chainlink_feed.caller({'from' : random_advancer.address, 'gas': 8000000}).getDailyVolatility( 1837 self.daily_vol_period * self.daily_period 1838 ) 1839 except Exception as inst: 1840 print(inst, "bad vol calc") 1841 continue 1842 1843 multiplier = 3.0 1844 tvol = (vol / (10.**EXCHG['decimals'])) 1845 normed_vol = math.log((current_price + (tvol * multiplier)) / (current_price - (tvol * multiplier))) 1846 1847 mcmc_data["pending"] = {} 1848 mcmc_data["pending"]["curr_price"] = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals']) 1849 mcmc_data["pending"]["vol"] = normed_vol 1850 mcmc_data["pending"]["data"] = [{ 1851 "strike": strike, 1852 "option_type": option_type, 1853 "symbol": "pending" 1854 }] 1855 1856 f.write(json.dumps(mcmc_data, indent=4)) 1857 1858 ''' 1859 EXECUTE MODEL HERE FOR ALL DATA 1860 ''' 1861 1862 ''' 1863 EXAMPLE: ./op_model "2000" "0.2" "3.0" 1864 ''' 1865 cmd = './op_model "%s" "%s" "%s"' % ( 1866 num_samples, 1867 0.05, 1868 months_to_exp, 1869 ) 1870 model_ret = str(execute_cmd(cmd)) 1871 1872 1873 ''' 1874 LOAD IN FILE AND MAP DATA TO PAIR 1875 ''' 1876 with open('mcmc_symbol_computation.json', 'r+') as f: 1877 mcmc_symbol_computation = json.loads(f.read()) 1878 if mcmc_symbol_computation: 1879 if mcmc_symbol_computation["pending"]: 1880 x0s = mcmc_symbol_computation["pending"]['x'] 1881 if len(x0s) == 0: 1882 continue 1883 1884 try: 1885 x = [Balance.from_tokens(round(x0,4), EXCHG['decimals']).to_wei() for x0 in x0s] 1886 y = mcmc_symbol_computation["pending"]['y0'] + mcmc_symbol_computation["pending"]['y1'] 1887 y = [Balance.from_tokens(round(y0,4), EXCHG['decimals']).to_wei() for y0 in y] 1888 print(x) 1889 print(y) 1890 except Exception as inst: 1891 print ('failed to add_symbol') 1892 continue 1893 1894 try: 1895 # must be the selected advancer or governane proposoal 1896 ads_hash = self.linear_liquidity_pool.add_symbol(seleted_advancer, self.btcusd_chainlink_feed.address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock) 1897 providerAvax.make_request("avax.issueBlock", {}) 1898 receipt = w3.eth.waitForTransactionReceipt(ads_hash, poll_latency=tx_pool_latency, timeout=600) 1899 tx_hashes.append({'type': 'add_symbol', 'hash': ads_hash}) 1900 except Exception as inst: 1901 logger.info({"agent": a.address, "error": inst, "action": "add_symbol", "strike": strike, "maturity": maturity, "x": x, "y": y, "normed_vol": normed_vol, "vol": vol}) 1902 elif action == "create_symbol": 1903 for sym in available_symbols: 1904 if sym not in tks_list_symbols: 1905 1906 try: 1907 cs_hash = self.options_exchange.create_symbol(a, sym, self.btcusd_chainlink_feed) 1908 providerAvax.make_request("avax.issueBlock", {}) 1909 receipt = w3.eth.waitForTransactionReceipt(cs_hash, poll_latency=tx_pool_latency, timeout=600) 1910 tx_hashes.append({'type': 'create_symbol', 'hash': cs_hash}) 1911 except Exception as inst: 1912 logger.info({"agent": a.address, "error": inst, "action": "create_symbol", "sym": sym }) 1913 continue 1914 elif action == "deposit_pool": 1915 amount = portion_dedusted( 1916 a.usdt, 1917 commitment * 0.1 1918 ) 1919 try: 1920 dpp_hash = self.linear_liquidity_pool.deposit_pool(a, amount) 1921 tx_hashes.append({'type': 'deposit_pool', 'hash': dpp_hash}) 1922 except Exception as inst: 1923 logger.info({"agent": a.address, "error": inst, "action": "deposit_pool", "amount": amount}) 1924 elif action == "withdraw": 1925 amount = portion_dedusted( 1926 exchange_free_bal, 1927 commitment * 10.0 1928 ) 1929 try: 1930 logger.info("Before Withdraw; volume: {} exchange_free_bal: {}".format(amount, exchange_free_bal)) 1931 wtd_hash = self.options_exchange.withdraw(a, amount) 1932 tx_hashes.append({'type': 'withdraw', 'hash': wtd_hash}) 1933 except Exception as inst: 1934 logger.info({"agent": a.address, "error": inst, "action": "withdraw", "amount": amount}) 1935 elif action == "redeem_pool": 1936 try: 1937 logger.info("Before Redeem Pool Shares") 1938 rdm_hash = self.linear_liquidity_pool.redeem_pool(a) 1939 tx_hashes.append({'type': 'redeem_pool', 'hash': rdm_hash}) 1940 except Exception as inst: 1941 logger.info({"agent": a.address, "error": inst, "action": "redeem_pool"}) 1942 elif action == "redeem_token": 1943 for otk, otv in self.option_tokens_expired.items(): 1944 if otv[a] > 0: 1945 try: 1946 logger.info("Before Redeem Option: {}, {}".format(otv.symbol, otv[a])) 1947 rdmt_hash = self.options_exchange.redeem_token(a, otk) 1948 tx_hashes.append({'type': 'redeem_token', 'hash': rdmt_hash}) 1949 except Exception as inst: 1950 logger.info({"agent": a.address, "error": inst, "action": "redeem_token", "option_token": otv.address}) 1951 elif action == "burn_token": 1952 ''' 1953 for otk, otv in self.option_tokens_expired_to_burn.items(): 1954 if otv[a] > 0: 1955 try: 1956 logger.info("Before Burn Expired Option: {}".format(otv.symbol)) 1957 rdmt_hash = self.options_exchange.redeem_token(a, otk) 1958 tx_hashes.append({'type': 'burn_token', 'hash': rdmt_hash}) 1959 except Exception as inst: 1960 logger.info({"agent": a.address, "error": inst, "action": "burn_token", "option_token": otv.address}) 1961 ''' 1962 1963 for otk, otv in self.option_tokens.items(): 1964 owv = Balance(otv.contract.caller({'from' : a.address, 'gas': 100000}).writtenVolume(a.address), EXCHG['decimals']) 1965 if owv > 0 and otv[a] > 0: 1966 # written > holding 1967 token_amount = Balance(owv.to_wei() - otv[a].to_wei(), EXCHG['decimals']) 1968 1969 if token_amount > 0: 1970 try: 1971 logger.info("Before Burn Excess Options: {}, token_amount: {}".format(otv.symbol, token_amount)) 1972 burn_hash = self.options_exchange.burn_token(a, otk, token_amount) 1973 tx_hashes.append({'type': 'burn_token', 'hash': burn_hash}) 1974 except Exception as inst: 1975 logger.info({"agent": a.address, "error": inst, "action": "burn_token", "option_token": otv.address}) 1976 1977 elif action == "write": 1978 # select from available symbols 1979 sym = available_symbols[int(random.random() * (len(available_symbols) - 1))] 1980 sym_parts = sym.split('-') 1981 1982 ''' 1983 * sym is something like: `ETH/USD-EC-13e20-1611964800` which represents an ETH european call option with strike price US$ 1300 and maturity at timestamp `1611964800`. 1984 ''' 1985 strike_price = int(float(sym_parts[2])) 1986 maturity = int(sym_parts[3]) 1987 option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL' 1988 amount = int(round(portion_dedusted( 1989 buyStock, 1990 commitment 1991 ))) 1992 1993 logger.info("Looking to Write; symbol: {}, strike_price: {}, amount: {}, exchange_bal: {}".format(sym, strike_price, amount, exchange_bal)) 1994 1995 try: 1996 cc = self.options_exchange.calc_collateral(a, self.btcusd_chainlink_feed.address, option_type, amount, strike_price, maturity) 1997 cc_s = self.options_exchange.calc_collateral_surplus(a, a) 1998 except Exception as inst: 1999 print(inst) 2000 continue 2001 2002 if cc_s < 1: 2003 continue 2004 2005 if(cc > cc_s): 2006 amount /= (cc.to_wei() / cc_s.to_wei()) 2007 logger.info("Norm to Write; amount: {}".format(amount)) 2008 2009 if amount < 1: 2010 continue 2011 2012 try: 2013 logger.info("Before Write; symbol: {}, strike_price: {}, amount: {}".format(sym, strike_price, amount)) 2014 w_hash = self.options_exchange.write(a, self.btcusd_chainlink_feed.address, option_type, amount, strike_price, maturity) 2015 tx_hashes.append({'type': 'write', 'hash': w_hash}) 2016 except Exception as inst: 2017 logger.info({"agent": a.address, "error": inst, "action": "write", "strike_price": strike_price, "maturity": maturity, "option_type": option_type, "amount": amount}) 2018 elif action == "buy": 2019 tks_list = list(self.option_tokens.values()) 2020 option_token_to_buy = tks_list[int(random.random() * (len(tks_list) - 1))] 2021 symbol = option_token_to_buy.contract.caller({'from' : a.address, 'gas': 8000000}).symbol() 2022 option_token_balance_of_pool = Balance( 2023 option_token_to_buy.contract.caller({'from' : a.address, 'gas': 8000000}).writtenVolume(self.linear_liquidity_pool.address), 2024 EXCHG['decimals'] 2025 ) 2026 print(symbol, option_token_balance_of_pool) 2027 try: 2028 current_price_volume = self.linear_liquidity_pool.query_buy(a, symbol) 2029 except Exception as inst: 2030 print("\terror querying buy", inst) 2031 continue 2032 2033 if option_token_balance_of_pool >= buyStock: 2034 continue 2035 2036 print(symbol, current_price_volume, option_token_balance_of_pool, exchange_bal, 'BUY') 2037 volume = Balance(current_price_volume[1], EXCHG['decimals']) 2038 2039 price = current_price_volume[0] 2040 2041 if (volume * (price / 10.**EXCHG['decimals']) * 100) > exchange_bal: 2042 ''' 2043 sym_parts = symbol.split('-') 2044 strike_price = int(float(sym_parts[2])) 2045 maturity = int(sym_parts[3]) 2046 option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL' 2047 try: 2048 cc = self.options_exchange.calc_collateral(a, self.btcusd_chainlink_feed.address, option_type, volume, strike_price, maturity) 2049 except: 2050 continue 2051 ''' 2052 mod_price = Balance(price, EXCHG['decimals']) 2053 print("mod_price", mod_price, mod_price.to_decimals(EXCHG['decimals']), mod_price.to_decimals(EXCHG['decimals']).to_wei(), exchange_bal.to_wei(), volume) 2054 volume = Balance.from_tokens(int((exchange_bal.to_wei() / mod_price.to_decimals(EXCHG['decimals']).to_wei()) / 10), EXCHG['decimals']) 2055 2056 2057 if volume < 1: 2058 volume = Balance.from_tokens(1, EXCHG['decimals']) 2059 2060 2061 try: 2062 logger.info("Before Buy; symbol: {}, price: {}, volume: {}".format(symbol, price, volume)) 2063 buy_hash = self.linear_liquidity_pool.buy(a, symbol, price, volume) 2064 providerAvax.make_request("avax.issueBlock", {}) 2065 tx_hashes.append({'type': 'buy', 'hash': buy_hash}) 2066 except Exception as inst: 2067 logger.info({"agent": a.address, "error": inst, "action": "buy", "volume": volume, "price": price, "symbol": symbol}) 2068 elif action == "sell": 2069 option_token_to_sell = None 2070 symbol = None 2071 volume_to_sell = Balance(0, EXCHG['decimals']) 2072 current_price_volume = None 2073 for k, ot in self.option_tokens.items(): 2074 volume_to_sell = Balance(ot.contract.caller({'from' : a.address, 'gas': 8000000}).balanceOf(a.address), EXCHG['decimals']) 2075 if volume_to_sell > 1: 2076 option_token_to_sell = ot 2077 2078 if not option_token_to_sell: 2079 continue 2080 2081 symbol = option_token_to_sell.contract.caller({'from' : a.address, 'gas': 8000000}).symbol() 2082 try: 2083 current_price_volume = self.linear_liquidity_pool.query_sell(a, symbol) 2084 except Exception as inst: 2085 print("\terror querying sell", inst) 2086 continue 2087 2088 if volume_to_sell >= sellStock: 2089 print(volume_to_sell, ">", sellStock, "CANT SELL") 2090 continue 2091 2092 break 2093 2094 if not current_price_volume: 2095 continue 2096 2097 2098 print(symbol, current_price_volume, volume_to_sell, a.total_written, a.total_holding, 'SELL') 2099 2100 if a.total_written > 0 and ((a.total_written >= a.total_holding) or (a.total_written < a.total_holding)): 2101 # for exchange writers 2102 volume = volume_to_sell 2103 elif a.total_holding > 0 and a.total_written == 0: 2104 # for traders 2105 volume = portion_dedusted( 2106 volume_to_sell, 2107 commitment 2108 ) 2109 2110 if volume == 0: 2111 volume = Balance.from_tokens(1, EXCHG['decimals']) 2112 price = current_price_volume[0] - (int(current_price_volume[0] * 0.001)) 2113 2114 try: 2115 logger.info("Before Sell; symbol: {}, price: {}, volume: {}".format(symbol, price, volume)) 2116 sell_hash = self.linear_liquidity_pool.sell(a, symbol, price, volume, option_token_to_sell) 2117 tx_hashes.append({'type': 'sell', 'hash': sell_hash, "volume": volume, "price": price, "symbol": symbol}) 2118 except Exception as inst: 2119 logger.info({"agent": a.address, "error": inst, "action": "sell", "volume": volume, "price": price, "symbol": symbol}) 2120 elif action == "liquidate": 2121 for short_owner in any_short_collateral: 2122 if short_owner.total_written > 1: 2123 for otk, otv in self.option_tokens.items(): 2124 if otv.contract.caller({'from' : a.address, 'gas': 8000000}).writtenVolume(short_owner.address) > 0: 2125 try: 2126 lqd8_hash = self.options_exchange.liquidate(a, otk, short_owner.address) 2127 tx_hashes.append({'type': 'liquidate', 'hash': lqd8_hash}) 2128 except Exception as inst: 2129 logger.info({"agent": a.address, "error": inst, "action": "liquidate", "short_owner": short_owner.address, "option_token": otk}) 2130 2131 self.has_tried_liquidating = True 2132 elif action == "liquidate_self": 2133 for otk, otv in self.option_tokens_expired.items(): 2134 if otv[a] > 0: 2135 try: 2136 lqd8_hash = self.options_exchange.liquidate(a, otk, self.linear_liquidity_pool.address) 2137 tx_hashes.append({'type': 'liquidate_self', 'hash': lqd8_hash}) 2138 except Exception as inst: 2139 logger.info({"agent": a.address, "error": inst, "action": "liquidate_self", "option_token": otk}) 2140 2141 for otk, otv in self.option_tokens.items(): 2142 if otv[a] > 0: 2143 try: 2144 lqd8_hash = self.options_exchange.liquidate(a, otk, a.address) 2145 tx_hashes.append({'type': 'liquidate_self', 'hash': lqd8_hash}) 2146 except Exception as inst: 2147 logger.info({"agent": a.address, "error": inst, "action": "liquidate_self", "short_owner": a.address, "option_token": otk}) 2148 2149 else: 2150 raise RuntimeError("Bad action: " + action) 2151 2152 anyone_acted = True 2153 else: 2154 # It's normal for agents other then the first to advance to not be able to act on block 0. 2155 pass 2156 2157 end_tx_count = a.next_tx_count 2158 2159 total_tx_submitted += (end_tx_count - start_tx_count) 2160 2161 providerAvax.make_request("avax.issueBlock", {}) 2162 2163 tx_hashes_good = 0 2164 tx_fails = [] 2165 tx_good = [] 2166 #''' 2167 for tmp_tx_hash in tx_hashes: 2168 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency, timeout=600) 2169 tx_hashes_good += receipt["status"] 2170 if receipt["status"] == 0: 2171 tx_fails.append(tmp_tx_hash) 2172 else: 2173 tx_good.append(tmp_tx_hash) 2174 2175 #''' 2176 2177 logger.info("total tx: {}, successful tx: {}, tx fails: {}, tx passed: {}".format( 2178 len(tx_hashes), tx_hashes_good, tx_fails, tx_good 2179 ) 2180 ) 2181 2182 return True, random_advancer, tx_good 2183 2184 def main(): 2185 """ 2186 Main function: run the simulation. 2187 """ 2188 global avax_cchain_nonces 2189 2190 ''' 2191 curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, "method" :"debug_increaseTime", "params" : ["0x45e8d9a7ca159a0f6957534cb25412b4daa4243906ef2b6e93125246b1e27d05"]}' -H 'content-type:application/json;' http://127.0.0.1:9545/ext/bc/C/rpc 2192 ''' 2193 transaction = w3.eth.get_transaction("0x5d385ebc44b8e1e6c01b24ef2a13fa574649d0993cae859a63af64ecc11f5851") 2194 2195 print(transaction.input) 2196 #print(provider.make_request("debug_traceTransaction", ["0x45e8d9a7ca159a0f6957534cb25412b4daa4243906ef2b6e93125246b1e27d05"])) 2197 #print(w3.eth.get_block('latest')['timestamp']) 2198 # 2199 2200 2201 logging.basicConfig(level=logging.INFO) 2202 logger.info('Total Agents: {}'.format(len(w3.eth.accounts[:max_accounts]))) 2203 2204 2205 options_exchange = w3.eth.contract(abi=OptionsExchangeContract['abi'], address=EXCHG["addr"]) 2206 proposal_wrapper = w3.eth.contract(abi=ProposalWrapperContract['abi'], address=PROPSWRPR["addr"]) 2207 proposal_manager = w3.eth.contract(abi=ProposalManagerContract['abi'], address=PROPSMNGER["addr"]) 2208 proposal_manager_helper = w3.eth.contract(abi=ProposalManagerHelperContract['abi'], address=PROPSMNGERHELPER["addr"]) 2209 usdt = TokenProxy(w3.eth.contract(abi=USDTContract['abi'], address=USDT["addr"])) 2210 credit_provider = w3.eth.contract(abi=CreditProviderContract['abi'], address=CREDPRO["addr"]) 2211 linear_liquidity_pool_factory = w3.eth.contract(abi=LinearLiquidityPoolFactoryContract['abi'], address=LLPF["addr"]) 2212 protocol_settings = w3.eth.contract(abi=ProtocolSettingsContract['abi'], address=STG['addr']) 2213 btcusd_chainlink_feed = w3.eth.contract(abi=ChainlinkFeedContract['abi'], address=BTCUSDc['addr']) 2214 btcusd_agg = w3.eth.contract(abi=AggregatorV3MockContract['abi'], address=BTCUSDAgg["addr"]) 2215 2216 #print(btcusd_chainlink_feed.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).getLatestPrice()) 2217 #sys.exit() 2218 2219 mock_time = w3.eth.contract(abi=TimeProviderMockContract['abi'], address=TPRO["addr"]) 2220 2221 2222 #'' 2223 #print(credit_provider.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).balanceOf(linear_liquidity_pool.address)) 2224 print("tx-input -> 0xa53b0041") 2225 print(proposal_wrapper.decode_function_input("0x5e4d3229")) 2226 #print(proposal_manager.decode_function_input(transaction.input)) 2227 2228 #print( 2229 #proposal_manager_helper.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).getExecutionBytes()) 2230 2231 2232 #sys.exit() 2233 2234 #'' 2235 ''' 2236 INIT FEEDS FOR BTCUSDAGG 2237 ''' 2238 btcusd_historical_ohlc = [] 2239 2240 ''' 2241 for acc in w3.eth.accounts[:max_accounts]: 2242 cs = options_exchange.caller({'from' : acc, 'gas': 8000000}).calcSurplus(acc) 2243 print(acc, cs) 2244 ''' 2245 #print(linear_liquidity_pool.caller({'from' : w3.eth.accounts[:max_accounts][0], 'gas': 8000000}).listExpiredSymbols()) 2246 #pretty(options_exchange.functions.resolveToken("BTC/USD-EP-147e18-1623989786").call(), indent=0) 2247 #print(linear_liquidity_pool.functions.totalSupply().call()) 2248 #print(Balance(4.474093538197649, 18).to_wei()) 2249 2250 #sys.exit() 2251 ''' 2252 with open('../../data/BTC-USD_vol_date_high_low_close.json', 'r+') as btcusd_file: 2253 btcusd_historical_ohlc = json.loads(btcusd_file.read())["chart"] 2254 2255 2256 daily_period = 60 * 60 * 24 2257 current_timestamp = int(w3.eth.get_block('latest')['timestamp']) 2258 print("current_timestamp", current_timestamp) 2259 2260 btcusd_answers = [] 2261 start_date = "2017-06-17"#"2017-12-17" 2262 btcusd_data_offset = 0 2263 btcusd_data_subtraction_set = 30 # look back period to present to seed data for vol calcs 2264 start_data_parsing = False # change this if you want to skip foward to specific time period 2265 for xidx, x in enumerate(btcusd_historical_ohlc): 2266 2267 2268 if start_data_parsing is False: 2269 if x['date'] == start_date: 2270 start_data_parsing = True 2271 btcusd_data_offset = xidx 2272 2273 2274 if x["open"] != 'null': 2275 btcusd_answers.append(int(float(x["open"]) * (10**BTCUSDAgg['decimals']))) 2276 2277 if btcusd_data_offset < btcusd_data_subtraction_set: 2278 btcusd_data_subtraction_set = btcusd_data_offset 2279 2280 btcusd_answers = btcusd_answers[btcusd_data_offset-btcusd_data_subtraction_set:] 2281 2282 print('btcusd_data_offset', btcusd_data_offset, start_date) 2283 ''' 2284 2285 avax_cchain_nonces = open(MMAP_FILE, "r+b") 2286 2287 tx_hashes = [] 2288 tx_hashes_good = 0 2289 tx_fails = [] 2290 2291 linear_liquidity_pool_address = None 2292 linear_liquidity_pool = None 2293 #'' 2294 # opx = OptionsExchange(options_exchange, usdt, btcusd_chainlink_feed) 2295 opx = OptionsExchange(options_exchange, usdt, []) 2296 if not linear_liquidity_pool_address: 2297 # temp opx, llp, agent 2298 agent = Agent(None, opx, None, None, usdt, starting_axax=0, starting_usdt=0, wallet_address=w3.eth.accounts[0], is_mint=False) 2299 ''' 2300 p_hash = transaction_helper( 2301 agent, 2302 opx.contract.functions.createPool( 2303 "DEFAULT", 2304 "TEST", 2305 ), 2306 8000000 2307 ) 2308 tmp_tx_hash = {'type': 'createPool', 'hash': p_hash} 2309 print(tmp_tx_hash) 2310 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2311 tx_hashes.append(tmp_tx_hash) 2312 tx_hashes_good += receipt["status"] 2313 if receipt["status"] == 0: 2314 print(receipt) 2315 tx_fails.append(tmp_tx_hash['type']) 2316 logs = options_exchange.events.CreatePool().processReceipt(receipt) 2317 print("linear pool address", logs[0].args.token) 2318 linear_liquidity_pool_address = logs[0].args.token 2319 linear_liquidity_pool = w3.eth.contract(abi=LinearLiquidityPoolContract["abi"], address=linear_liquidity_pool_address) 2320 ''' 2321 else: 2322 linear_liquidity_pool = w3.eth.contract(abi=LinearLiquidityPoolContract["abi"], address=linear_liquidity_pool_address) 2323 ''' 2324 _llp = LinearLiquidityPool(linear_liquidity_pool, usdt, opx) 2325 agent = Agent(_llp, opx, None, None, usdt, starting_axax=0, starting_usdt=0, wallet_address=w3.eth.accounts[0], is_mint=False) 2326 ''' 2327 ''' 2328 BELOW: USE FOR TESTNET FUNDING OF FAKE STABLECOIN 2329 ''' 2330 2331 sat_hash = transaction_helper( 2332 agent, 2333 protocol_settings.functions.setAllowedToken( 2334 usdt.address, 2335 1, 2336 10**(18 - usdt.decimals) 2337 ), 2338 500000 2339 ) 2340 tmp_tx_hash = {'type': 'setAllowedToken', 'hash': sat_hash} 2341 tx_hashes.append(tmp_tx_hash) 2342 print(tmp_tx_hash) 2343 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2344 tx_hashes_good += receipt["status"] 2345 if receipt["status"] == 0: 2346 print(receipt) 2347 tx_fails.append(tmp_tx_hash['type']) 2348 2349 2350 2351 msp_hash= transaction_helper( 2352 agent, 2353 protocol_settings.functions.setMinShareForProposal( 2354 10, 2355 1000 2356 ), 2357 500000 2358 ) 2359 tmp_tx_hash = {'type': 'setMinShareForProposal', 'hash': msp_hash} 2360 tx_hashes.append(tmp_tx_hash) 2361 print(tmp_tx_hash) 2362 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2363 tx_hashes_good += receipt["status"] 2364 if receipt["status"] == 0: 2365 print(receipt) 2366 tx_fails.append(tmp_tx_hash['type']) 2367 2368 2369 2370 sys.exit() 2371 2372 ''' 2373 ABOVE: FR TESTNET FUNDING OF FAKE STABLECOIN 2374 ''' 2375 2376 2377 ''' 2378 SETUP POOL: 2379 All options must have maturities under the pool maturity 2380 ''' 2381 2382 2383 pool_spread = 5 * (10**7) #5% 2384 pool_reserve_ratio = 0 * (10**7) # 20% default 2385 pool_maturity = (1000000000 * daily_period) + current_timestamp 2386 2387 ''' 2388 SETUP PROTOCOL SETTINGS FOR POOL 2389 ''' 2390 skip = False 2391 2392 if not skip: 2393 mt_hash = transaction_helper( 2394 agent, 2395 mock_time.functions.setFixedTime( 2396 -1 2397 ), 2398 500000 2399 ) 2400 tmp_tx_hash = {'type': 'setFixedTime', 'hash': mt_hash} 2401 tx_hashes.append(tmp_tx_hash) 2402 print(tmp_tx_hash) 2403 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2404 tx_hashes_good += receipt["status"] 2405 if receipt["status"] == 0: 2406 print(receipt) 2407 tx_fails.append(tmp_tx_hash['type']) 2408 2409 if not skip: 2410 sp_hash = transaction_helper( 2411 agent, 2412 linear_liquidity_pool.functions.setParameters( 2413 pool_spread, 2414 pool_reserve_ratio, 2415 pool_maturity 2416 ), 2417 500000 2418 ) 2419 tmp_tx_hash = {'type': 'setParameters', 'hash': sp_hash} 2420 tx_hashes.append(tmp_tx_hash) 2421 print(tmp_tx_hash) 2422 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2423 tx_hashes_good += receipt["status"] 2424 if receipt["status"] == 0: 2425 print(receipt) 2426 tx_fails.append(tmp_tx_hash['type']) 2427 2428 if not skip: 2429 so_hash = transaction_helper( 2430 agent, 2431 protocol_settings.functions.setOwner( 2432 agent.address, 2433 ), 2434 500000 2435 ) 2436 tmp_tx_hash = {'type': 'setOwner', 'hash': so_hash} 2437 tx_hashes.append(tmp_tx_hash) 2438 print(tmp_tx_hash) 2439 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2440 tx_hashes_good += receipt["status"] 2441 if receipt["status"] == 0: 2442 print(receipt) 2443 tx_fails.append(tmp_tx_hash['type']) 2444 2445 2446 if not skip: 2447 sat_hash = transaction_helper( 2448 agent, 2449 protocol_settings.functions.setAllowedToken( 2450 usdt.address, 2451 1, 2452 10**(18 - usdt.decimals) 2453 ), 2454 500000 2455 ) 2456 tmp_tx_hash = {'type': 'setAllowedToken', 'hash': sat_hash} 2457 tx_hashes.append(tmp_tx_hash) 2458 print(tmp_tx_hash) 2459 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2460 tx_hashes_good += receipt["status"] 2461 if receipt["status"] == 0: 2462 print(receipt) 2463 tx_fails.append(tmp_tx_hash['type']) 2464 2465 if not skip: 2466 suf_hash = transaction_helper( 2467 agent, 2468 protocol_settings.functions.setUdlFeed( 2469 btcusd_chainlink_feed.address, 2470 1 2471 ), 2472 500000 2473 ) 2474 tmp_tx_hash = {'type': 'setUdlFeed', 'hash': suf_hash} 2475 tx_hashes.append(tmp_tx_hash) 2476 print(tmp_tx_hash) 2477 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2478 tx_hashes_good += receipt["status"] 2479 if receipt["status"] == 0: 2480 print(receipt) 2481 tx_fails.append(tmp_tx_hash['type']) 2482 2483 if not skip: 2484 svp_hash = transaction_helper( 2485 agent, 2486 protocol_settings.functions.setVolatilityPeriod( 2487 30 * daily_period 2488 ), 2489 500000 2490 ) 2491 tmp_tx_hash = {'type': 'setVolatilityPeriod', 'hash': svp_hash} 2492 tx_hashes.append(tmp_tx_hash) 2493 print(tmp_tx_hash) 2494 receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency) 2495 tx_hashes_good += receipt["status"] 2496 if receipt["status"] == 0: 2497 print(receipt) 2498 tx_fails.append(tmp_tx_hash['type']) 2499 2500 2501 2502 logger.info("total setup tx: {}, successful setup tx: {}, setup tx fails: {}".format( 2503 len(tx_hashes), tx_hashes_good, json.dumps(tx_fails) 2504 ) 2505 ) 2506 2507 # Make a model of the options exchnage 2508 start_init = time.time() 2509 logger.info('INIT STARTED') 2510 model = Model(options_exchange, credit_provider, linear_liquidity_pool, btcusd_chainlink_feed, btcusd_agg, btcusd_answers, None, usdt, w3.eth.accounts[:max_accounts], min_faith=0.5E6, max_faith=1E6, use_faith=False) 2511 end_init = time.time() 2512 logger.info('INIT FINISHED {} (s)'.format(end_init - start_init)) 2513 2514 # Make a log file for system parameters, for analysis 2515 stream = open("log.tsv", "a+") 2516 2517 for i in range(50000): 2518 # Every block 2519 # Try and tick the model 2520 start_iter = time.time() 2521 2522 (anyone_acted, seleted_advancer, tx_passed) = model.step() 2523 if not anyone_acted: 2524 # Nobody could act 2525 logger.info("Nobody could act") 2526 break 2527 end_iter = time.time() 2528 logger.info('iter: %s, sys time %s' % (i, end_iter-start_iter)) 2529 # Log system state 2530 model.log(stream, seleted_advancer, header=(i == 0)) 2531 2532 filtered_tx_passed = list(set([x['type'] for x in tx_passed])) 2533 2534 if len(filtered_tx_passed) == 1: 2535 provider.make_request("debug_increaseTime", [3600 * 24]) 2536 else: 2537 provider.make_request("debug_increaseTime", [3600 * 6]) 2538 #''' 2539 #sys.exit() 2540 2541 if __name__ == "__main__": 2542 main()