Advertisement
Guest User

Untitled

a guest
May 14th, 2012
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Trying to simulate a simple economy, based on Doran & Parberry (2010)
  2.  
  3. from __future__ import division
  4. import random
  5. import cProfile
  6. import pstats
  7.  
  8. break_chances = {'tools':5, 'wood':10}
  9.  
  10. HIST_WINDOW_SIZE = 10 # Amount of trades auctions keep in memory to determine avg price (used by agents)
  11. MAX_INVENTORY_SIZE = 15 # A not-really-enforced max inventory limit
  12.  
  13. PROFIT_MARGIN = 1.5 # Won't sell an item for lower than this * normalized production cost
  14. STARTING_GOLD = 1000 # How much gold each agent starts with
  15. TAXES = 2 # How much gold is payed in taxes per turn
  16.  
  17. MIN_CERTAINTY_VALUE = 8 ## Within what range traders are limited to estimating the market price
  18. BID_REJECTED_ADJUSTMENT = 5 # Adjust prices by this much when our bid is rejected
  19. ASK_REJECTED_ADJUSTMENT = 5 # Adjust prices by this much when nobody buys our stuff
  20. REJECTED_UNCERTAINTY_AMOUNT = 2 # We get this much more uncertain about a price when an offer is rejected
  21. ACCEPTED_CERTAINTY_AMOUNT = 1  # Out uncertainty about a price decreases by this amount when an offer is accepted
  22.  
  23. P_DIF_ADJ = 3  # When offer is accepted and exceeds what we thought the price value was, adjust it upward by this amount
  24. N_DIF_ADJ = 3  # When offer is accepted and is lower than what we thought the price value was, adjust it downward by this amount
  25. P_DIF_THRESH = 2  #Threshhold at which adjustment of P_DIF_ADJ is added to the perceived value of a commodity
  26. N_DIF_THRESH = 1.6 #Threshhold at which adjustment of P_DIF_ADJ is subtracted from the perceived value of a commodity
  27.  
  28. START_VAL = 50 # Arbitrary value that sets the starting price of good (in gold)
  29. START_UNCERT = 10 # Arbitrary uncertainty value (agent believes price is somewhere between (mean-this, mean+this)
  30.  
  31. def roll(a, b):
  32.     return random.randint(a, b)
  33.    
  34. class Value:
  35.     # Agents' perceived values of objects
  36.     def __init__(self, center, uncertainty):
  37.         self.center = center
  38.         self.uncertainty = uncertainty
  39.        
  40. class Auction:
  41.     # Seperate "auction" for each commodity
  42.     # Runs each round of bidding, as well as archives historical price info
  43.     def __init__(self, commodity):
  44.         self.commodity = commodity
  45.         self.bids = []
  46.         self.asks = []
  47.         self.price_history = []
  48.         self.mean_price = START_VAL
  49.    
  50.     def update_mean_price(self):
  51.         # update the mean price for this commodity by averaging over the last HIST_WINDOW_SIZE items
  52.         self.mean_price = int(round(sum(self.price_history[-HIST_WINDOW_SIZE:])/len(self.price_history[-HIST_WINDOW_SIZE:])))
  53.        
  54. class Offer:
  55.     # An offer that goes into the auction's "bids" or "asks".
  56.     # Bids and asks are then compared against each other and agents meet at the middle
  57.     def __init__(self, owner, commodity, price, quantity):
  58.         self.owner = owner
  59.         self.commodity = commodity
  60.         self.price = price
  61.         self.quantity = quantity
  62.        
  63.        
  64. class Agent:
  65.     # An agent in the economy. They produce items, make bids and sell requests, and pay taxes
  66.     def __init__(self, name, output, required_items, used_items, consumed_items):
  67.         global num_agents
  68.         # Add self to list of agents
  69.         agents.append(self)
  70.        
  71.         self.name = name
  72.         self.output = output #The product this agent produces (dict of items:amounts)
  73.         self.required_items = required_items #Things you need, but don't use up in item creation (dict of items:amounts)
  74.         self.used_items = used_items #Things you transform into the product (dict of items:amounts)
  75.         self.consumed_items = consumed_items #Things you consume once per turn (food, etc)
  76.        
  77.         self.inventory_size = MAX_INVENTORY_SIZE
  78.         self.gold = STARTING_GOLD
  79.        
  80.         ## Create a dict of all things we use. We won't sell anything below the price to produce it, hopefully
  81.         self.inventory = []
  82.         self.perceived_values = {} ## dict of what we believe the true price of an item is
  83.        
  84.         for item, amount in self.required_items.iteritems():
  85.             self.perceived_values[item] = Value(START_VAL, START_UNCERT)
  86.             for num in range(amount):
  87.                 self.inventory.append(item)
  88.                
  89.         for item, amount in self.consumed_items.iteritems():
  90.             self.perceived_values[item] = Value(START_VAL, START_UNCERT)
  91.             for num in range(amount):
  92.                 self.inventory.append(item)
  93.                
  94.         for item, amount in self.used_items.iteritems():
  95.             self.perceived_values[item] = Value(START_VAL, START_UNCERT)
  96.             for num in range(amount):
  97.                 self.inventory.append(item)
  98.                
  99.         ## Start off with 2x the stuff that you produce so you can begin selling right away
  100.         self.num_produced_items = 0
  101.         for item, amount in self.output.iteritems():
  102.             self.perceived_values[item] = Value(START_VAL, START_UNCERT)
  103.            
  104.             self.num_produced_items += amount
  105.             for num in range(amount*2):
  106.                 self.inventory.append(item)
  107.            
  108.     def produce_items(self):
  109.         # Verbose way to test if we have the correct items
  110.         can_produce = True
  111.         for item, amount in self.required_items.iteritems():
  112.             if self.inventory.count(item) < amount:
  113.                 can_produce = False
  114.                 break
  115.         if len(self.used_items) > 0:
  116.             for item, amount in self.used_items.iteritems():
  117.                 if self.inventory.count(item) < amount:
  118.                     can_produce = False
  119.                     break
  120.         # For items like food that are consumed on the side
  121.         if len(self.consumed_items) > 0:
  122.             for item, amount in self.consumed_items.iteritems():
  123.                 if self.inventory.count(item) < amount:
  124.                     can_produce = False
  125.                     break
  126.        
  127.         ## If we can produce stuff, then actually do it
  128.         if can_produce:
  129.             # Remove the used_items in the inv.
  130.             for item, amount in self.used_items.iteritems():
  131.                 for x in xrange(amount):
  132.                     self.inventory.remove(item)
  133.             # Produce stuff
  134.             for item, amount in self.output.iteritems():
  135.                 for x in xrange(amount):
  136.                     self.inventory.append(item)
  137.             # Stuff can break:
  138.             for item, amount in self.required_items.iteritems():
  139.                 for x in xrange(amount):
  140.                     if roll(1, break_chances[item]) == 1:
  141.                         self.inventory.remove(item)
  142.             # Consume stuff
  143.             for item, amount in self.consumed_items.iteritems():
  144.                 for x in xrange(amount):
  145.                     self.inventory.remove(item)
  146.        
  147.            
  148.     def has_space(self, amount):
  149.         return (self.inventory_size - len(self.inventory)) > amount
  150.            
  151.     def determine_purchase_quantity(self, commodity):
  152.         # First find historical mean price and our observed mean prices
  153.         historical_mean = auctions[commodity].mean_price
  154.         our_belief = self.perceived_values[commodity].center
  155.         # Pseudocode from Doran & Parberry (2010):
  156.         #favorability = max price - position of mean within observed trading range
  157.        
  158.         # I guess this should be a float, >1 means good, <1 means bad. Will adjust requested amt accordingly
  159.         favorability = our_belief/historical_mean
  160.        
  161.         #Force them to bid at least 1, for now
  162.         available_space = self.inventory_size - len(self.inventory)
  163.         return max(int(round(favorability * available_space)), 1)
  164.    
  165.    
  166.     def determine_sale_quantity(self, commodity):
  167.         # First find historical mean price and our observed mean prices
  168.         historical_mean = auctions[commodity].mean_price
  169.         our_belief = self.perceived_values[commodity].center
  170.        
  171.         # Pseudocode from Doran & Parberry (2010):
  172.         #favorability = max price - position of mean within observed trading range
  173.        
  174.         # I guess this should be a float, >1 means good, <1 means bad. Will adjust requested amt accordingly
  175.         favorability = historical_mean/our_belief
  176.         #Force them to sell at least 1, for now
  177.         return max(int(round(favorability * (self.inventory.count(commodity)))), 1)
  178.    
  179.    
  180.     def has(self, commodity, amount=1):
  181.         return self.inventory.count(commodity) >= amount
  182.        
  183.     def eval_need(self):
  184.         # See if we need items to continue producing our goods
  185.         for item, amount in self.used_items.iteritems():
  186.             if not self.has(item, amount):
  187.                 self.create_bid(self, item, amount)
  188.    
  189.         for item, amount in self.required_items.iteritems():
  190.             if not self.has(item, amount):
  191.                 self.create_bid(self, item, amount)
  192.  
  193.         for item, amount in self.consumed_items.iteritems():
  194.             if not self.has(item, amount):
  195.                 self.create_bid(self, item, amount)
  196.                
  197.                
  198.     def eval_sell(self):
  199.         # If we have stuff we've made, offer to sell it
  200.         for item, amount in self.output.iteritems():
  201.             if self.inventory.count(item) > 0:
  202.                 self.create_ask(self, item, self.inventory.count(item))
  203.    
  204.     def create_bid(self, agent, commodity, limit):
  205.         # If we decide to bid, here's how we do it
  206.         est_price = self.perceived_values[commodity].center
  207.         uncertainty = self.perceived_values[commodity].uncertainty
  208.         bid_price = roll(est_price - uncertainty, est_price + uncertainty)
  209.         if bid_price > self.gold:
  210.             bid_price = self.gold
  211.            
  212.         # Find ideal amount - should we pass bid price to this and compare that to hist. mean?
  213.         ideal_quantity = self.determine_sale_quantity(commodity)
  214.         # Determine quantity
  215.         quantity_to_buy = min(ideal_quantity, limit)
  216.        
  217.         if quantity_to_buy > 0:
  218.             auctions[commodity].bids.append(Offer(owner=agent, commodity=commodity, price=bid_price, quantity=quantity_to_buy))
  219.        
  220.    
  221.     def check_production_cost(self):
  222.         ### Might change this to reflect agent's own beliefs about price, rather than historical avgs ###
  223.         production_cost = 0
  224.        
  225.         # Cost of used_items is historical mean * the amount we use (since they are used up every
  226.         # time we need to make something, we're using the full value of the items)
  227.         for item, amount in self.used_items.iteritems():
  228.             production_cost += (auctions[item].mean_price * amount)
  229.            
  230.         # These items are required for production, but not used. Each one has a break chance, so
  231.         # the cost of buying one divided by the break chance is the avg cost to buy
  232.         for item, amount in self.required_items.iteritems():
  233.             production_cost += int(round(((auctions[item].mean_price * amount)/break_chances[item])))
  234.            
  235.         # These items are also used each time we make something, so the full cost of the items are used
  236.         for item, amount in self.consumed_items.iteritems():
  237.             production_cost += auctions[item].mean_price * amount
  238.            
  239.         # Take into account the taxes we pay
  240.         production_cost += TAXES
  241.        
  242.         return production_cost
  243.        
  244.     def create_ask(self, agent, commodity, limit)
  245.         # Determines how many items to sell, and at what cost
  246.         production_cost = self.check_production_cost()
  247.         min_sale_price = int(round((production_cost/self.num_produced_items)*PROFIT_MARGIN))
  248.        
  249.         est_price = self.perceived_values[commodity].center
  250.         uncertainty = self.perceived_values[commodity].uncertainty
  251.         # won't go below what they paid for it 
  252.         ask_price = max(roll(est_price - uncertainty, est_price + uncertainty), min_sale_price)
  253.        
  254.         ideal_quantity = self.determine_purchase_quantity(commodity)
  255.         # Paper says should be max(ideal, limit), but that creates an offer bigger than what we have in inventory?
  256.         quantity_to_sell = min(ideal_quantity, limit)
  257.        
  258.         if quantity_to_sell > 0:
  259.             auctions[commodity].asks.append(Offer(owner=agent, commodity=commodity, price=ask_price, quantity=quantity_to_sell))
  260.  
  261.            
  262.     def eval_trade_accepted(self, commodity, price):   
  263.         # Then, adjust our belief in the price
  264.         if self.perceived_values[commodity].uncertainty >= MIN_CERTAINTY_VALUE:
  265.             self.perceived_values[commodity].uncertainty -= ACCEPTED_CERTAINTY_AMOUNT
  266.        
  267.         our_mean = (self.perceived_values[commodity].center)
  268.        
  269.         if price > our_mean * P_DIF_THRESH:
  270.             self.perceived_values[commodity].center += P_DIF_ADJ
  271.         elif price < our_mean * N_DIF_THRESH:
  272.             # We never let it's worth drop under a certain % of tax money.
  273.             self.perceived_values[commodity].center = max(self.perceived_values[commodity].center - N_DIF_ADJ, TAXES*5 + self.perceived_values[commodity].uncertainty)
  274.  
  275.  
  276.     def eval_ask_rejected(self, commodity, price):
  277.         # What to do when we put something up for sale and nobody bought it
  278.         self.perceived_values[commodity].center -= ASK_REJECTED_ADJUSTMENT
  279.         self.perceived_values[commodity].uncertainty += REJECTED_UNCERTAINTY_AMOUNT
  280.            
  281.     def eval_bid_rejected(self, commodity, price):
  282.         # What to do when we've bid on something and didn't get it
  283.         self.perceived_values[commodity].center += BID_REJECTED_ADJUSTMENT
  284.         self.perceived_values[commodity].uncertainty += REJECTED_UNCERTAINTY_AMOUNT
  285.  
  286.    
  287. def add_new_agent():
  288.     # Create a new agent
  289.     num = roll(1, 5)
  290.    
  291.     if num == 1: farmer = Agent(name='farmer', output={'food':4}, required_items={'wood':1, 'tools':1}, used_items={}, consumed_items={})
  292.     elif num == 2: miner = Agent(name='miner', output={'ore':1}, required_items={'tools':1}, used_items={}, consumed_items={'food':1})
  293.     elif num == 3: refiner = Agent(name='refiner', output={'metal':1}, required_items={'tools':1}, used_items={'ore':1}, consumed_items={'food':1})
  294.     elif num == 4: woodcutter = Agent(name='woodcutter', output={'wood':2}, required_items={'tools':1}, used_items={}, consumed_items={'food':1})
  295.     elif num == 5: blacksmith = Agent(name='blacksmith', output={'tools':2}, required_items={}, used_items={'metal':1, 'wood':1}, consumed_items={'food':1})
  296.    
  297.  
  298. def create_agents(num_agents):
  299.     global agents
  300.     ## Create some agents ##
  301.     agents = []
  302.     for iteration in range(num_agents):
  303.         farmer = Agent(name='farmer', output={'food':4}, required_items={'wood':1, 'tools':1}, used_items={}, consumed_items={})
  304.         miner = Agent(name='miner', output={'ore':4}, required_items={'tools':1}, used_items={}, consumed_items={'food':1})
  305.         refiner = Agent(name='refiner', output={'metal':1}, required_items={'tools':1}, used_items={'ore':1}, consumed_items={'food':1})
  306.         woodcutter = Agent(name='woodcutter', output={'wood':2}, required_items={'tools':1}, used_items={}, consumed_items={'food':1})
  307.         blacksmith = Agent(name='blacksmith', output={'tools':1}, required_items={}, used_items={'metal':1, 'wood':1}, consumed_items={'food':1})
  308.  
  309. def run_simulation(num_rounds):
  310.     global agents
  311.     ## Run a simulation ##
  312.     for iteration in range(num_rounds):
  313.         prices = {'wood':[], 'food':[], 'ore':[], 'metal':[], 'tools':[]}
  314.         # First, each agent does what they do
  315.         for agent in reversed(agents):
  316.             agent.produce_items()
  317.             agent.eval_sell()
  318.             agent.gold -= TAXES
  319.             if agent.gold > 0:
  320.                 agent.eval_need()
  321.             else:
  322.                 agents.remove(agent)
  323.                 #print('Removing a ' + agent.name)
  324.                 add_new_agent()
  325.                
  326.         ## Run the auction
  327.         for commodity, auction in auctions.iteritems():
  328.             # Remove any bias? Not sure how much this does, if we're sorting them by price anyway
  329.             random.shuffle(auction.bids)
  330.             random.shuffle(auction.asks)
  331.             ## Sort the bids by price (highest to lowest) ##
  332.             auction.bids = sorted(auction.bids, key=lambda attr: attr.price, reverse=True)
  333.             ## Sort the asks by price (lowest to hghest) ##
  334.             auction.asks = sorted(auction.asks, key=lambda attr: attr.price)
  335.  
  336.             num_bids = len(auction.bids)
  337.             num_asks = len(auction.asks)
  338.            
  339.             while not len(auction.bids) == 0 and not len(auction.asks) == 0:
  340.                 buyer = auction.bids[0]
  341.                 seller = auction.asks[0]
  342.                 # Determine price/amount
  343.                 quantity = min(buyer.quantity, seller.quantity)
  344.                
  345.                 price = int(round((buyer.price + seller.price)/2))
  346.                
  347.                 if quantity > 0:
  348.                     # Adjust buyer/seller requested amounts
  349.                     buyer.quantity -= quantity
  350.                     seller.quantity -= quantity
  351.                    
  352.                     buyer.owner.eval_trade_accepted(buyer.commodity, price)
  353.                     seller.owner.eval_trade_accepted(buyer.commodity, price)
  354.                    
  355.                     ## Update inventories and gold counts
  356.                     for i in range(quantity):
  357.                         buyer.owner.inventory.append(buyer.commodity)
  358.                         seller.owner.inventory.remove(seller.commodity)
  359.                    
  360.                     buyer.owner.gold -= (price*quantity)
  361.                     seller.owner.gold += (price*quantity)
  362.                    
  363.                 # Add to running tally of prices this turn
  364.                 prices[commodity].append(price)
  365.                
  366.                 # Now that a transaction has occured, bump out the buyer or seller if either is satasfied
  367.                 if seller.quantity == 0: del auction.asks[0]
  368.                 if buyer.quantity == 0: del auction.bids[0]
  369.                    
  370.             # All bidders re-evaluate prices - currently too simplistic
  371.             if len(auction.bids) > 0:
  372.                 for buyer in auction.bids:
  373.                     buyer.owner.eval_bid_rejected(buyer.commodity, None)
  374.                     auctions[commodity].bids = []
  375.            
  376.             # All sellers re-evaluate prices - too simplistic as well
  377.             elif len(auction.asks) > 0:
  378.                 for seller in auction.asks:
  379.                     seller.owner.eval_bid_rejected(seller.commodity, None) 
  380.                     auctions[commodity].asks = []
  381.                    
  382.             ## Average prices      
  383.             if len(prices[commodity]) > 0:
  384.                 price_mean = int(round(sum(prices[commodity])/len(prices[commodity])))
  385.                 auction.price_history.append(price_mean)
  386.                 # Track mean price for last N turns
  387.                 auction.update_mean_price()
  388.                 #print (auction.commodity + ': ' + str(auction.mean_price) + '. This round: ' +
  389.                 #str(len(prices[commodity])) + ' ' + commodity + ' averaged at $' + str(price_mean) +
  390.                 #' (' + str(num_bids) + ' bids, ' + str(num_asks) + ' asks)')
  391.             else:
  392.                 auction.price_history.append(auction.price_history[-1])
  393.                 #print (commodity + ' was not sold this round (' + str(num_bids) + ' bids, ' + str(num_asks) + ' asks)')
  394.  
  395.  
  396. auctions = {'food':Auction('food'), 'wood':Auction('wood'), 'ore':Auction('ore'), 'metal':Auction('metal'), 'tools':Auction('tools')}
  397.  
  398.  
  399. create_agents(20)
  400. #cProfile.run('run_simulation(1)', 'economy')
  401. run_simulation(200)
  402.                
  403. for auction in auctions.itervalues():
  404.     print auction.commodity, auction.price_history
  405.  
  406. agent_nums = {'farmer':0, 'woodcutter':0, 'miner':0, 'blacksmith':0, 'refiner':0}
  407. for agent in agents:
  408.     agent_nums[agent.name] += 1
  409.     #print(agent.name + ': ' + str(agent.gold))
  410.    
  411. print str(len(agents)), agent_nums
  412.  
  413. #p = pstats.Stats('economy')
  414. #p.sort_stats('cumulative').print_stats(37)
  415. #p.sort_stats('time').print_stats(10)
  416. #p.sort_stats('file').print_stats('__init__')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement