CoCalc Public Filesweek 2_3 / assignment / MMS.ipynb
Authors: Remedios Lomax, Ben Murphy
Views : 86
Compute Environment: Ubuntu 20.04 (Default)

# Math Mock Stock

Welcome to MathMockStock! Here, you and your partner are going to form a company for trading on our stock market. In competition with all the other teams, can you make your fortune?

You will work as a team of 2 with your partner. The first step:

1. Go to the website and register your team by following the 'Team Registration' link.

Most importantly, you will have to select a unique 3-letter code that identifies your team. You will also get to choose a password. This password is not stored securely. Do not use one of your normal passwords. Just pick something easy, and don't use any interesting symbols (you might regret it later). This is just intended as a simple measure to prevent other teams putting in bids on your behalf.

1. You should enter your three letter team code as the variable TEAMCODE. Also give your student ID (StudentID), that of your partner (PartnerID) and your partner's name (PartnerName).
Marks: 2
In [2]:
StudentID=100889649 PartnerID=100894208 PartnerName="Remedios Lomax" TEAMCODE="CBR" #put YOUR team code here
Test your code from above here(2 points), ID: check_teamcode
In [ ]:

## Basic Summary

• This stock market only has 1 stock. That stock has a certain value which will fluctuate over the game.
• The stock value is set to £100 initially, and is always a natural number.
• You start the game with no stocks and £10,000. If there are $n$ teams, the bank starts with $80n$ shares.
• The game is round-based. In each round, each team submits buy/sell requests. You may either buy or sell (or do nothing), but not both.
• If you are buying, you must choose the amount that you are willing to pay for each share (we call this the bid price), and you may submit multiple buy requests.
• If you wish to do nothing, you must submit a "sell 0" command.
• When you sell, you say how many shares you want to sell, but you have no choice over what price you get for your shares.
• The new value of the stock is calculated based on the sale price, using a protocol described below. This is a very arbitrary model (It's even debatable whether this is a stock market, but the important thing is that it's a game with rules that you can analyse/manipulate etc.), but is supposed to represent the principle that stocks are worth what somebody is willing to pay for them. If there are buyers, what they will pay determines the stock price. If there aren't enough buyers, prices must drop. If there aren't enough sellers, prices must go up.

## Understand The Game

1. We will first run an entirely manual version of this game, for just 5 rounds. You will enter buy/sell requests, just to get a feel for how the system works. You might start discussing strategy at this point, but this particular game is irrelevant. Go to the website.
• To find out the values of the parameters, follow the link "get current status". Put your team's code in the text box next to the link to get personalised information. This data is presented to you in a format known as JSON. It's becoming quite common for a file format, particularly in the open source community. Python basically interprets it as a dictionary. Note that this page also tells you an extra parameter mode. If mode is 0, moves are being accepted. If the mode is 1, the round has finished, moves are being processed, and a new round will start soon.
• Submit your move through the website by following the 'submit a trade' link. If your move is allowed, you will get a return message of 'Success!'.
• You probably want to have two tabs open (perhaps even on neighbouring computers), one where you have followed the 'Submit a Trade' link, and the other where you have followed the 'Get Current Status' link, having put your team code in the text box.

## Coding

We want to automate the procedure, which we divide into three components: (i) getting the current status (i.e. how much money and how many stocks your team has), (ii) decide what you want your move to be, (iii) submit the trade. We'll start with the second of these by writing a function DecideTrade. Before you do that, you need to understand what is expected. To this end, you should write a doTests() function comprising at least 3 different assert statements that describe what DecideTrade should do in certain circumstances (not including the example). One example has been given for you. doTests() must return True if it successfully completes all the assert statements.

1. Understand (from the following bullets) how the DecideTrade function ought to run. Hence write tests, as a series of assert statements inside doTests that correctly verifies its operation (first cell, below).

The DecideTrade function should accept four parameters: round number, current stock price, how many shares your team has, and how much money your team has.

• It must decide three properties: (i) action must be either b (buy) or s (sell); (ii) quantity must be the non-negative integer number of shares that you want to buy or sell; (iii) bidprice is the positive integer value that you are willing to pay for each share (only if you are buying).
• The output must be a list of strings of text in a particular format (examples below). If you want to make multiple purchases, each entry in the list will be a different purchase request. Even if you do not use multiple purchases, you must return a list (of length 1), not a single string.
• If you want to 'do nothing', your code should return a sell 0 request.
• You cannot buy 0 shares.
• For now, we just start with a very simple function: if you have some shares, and the stock price is above 120, sell all shares; if the stock price is below 80, offer to buy as many shares as possible at the stock price; otherwise, do nothing.
• Your code must also function correctly if the round number is supplied as "test" rather than an integer.

Here are four examples of the format of the output text (before composing in a list):

round=99&action=s&quantity=10
round=5&action=b&quantity=8&bidprice=100
round=9&action=s&quantity=0
round=test&action=b&quantity=100&bidprice=5
The first conveys "sell 10 shares" as the move for round 99. The second conveys "offer to buy 8 shares at 100 per share" as the move for round 5. The third says "do nothing on round 9". The last offers to buy 100 shares at 5 per share, but is not applied to a particular round number, and is a test sequence instead.
Marks: 3
In [13]:
--------------------------------------------------------------------------- NotImplementedError Traceback (most recent call last) <ipython-input-13-965b8ae2ab63> in <module> 4 assert DecideTrade(1,100,0,10000)==['round=59&action=s&quantity=5'] 5 assert DecideTrade(1,5,0,10000)==['round=test&action=b&quantity=2'] ----> 6 doTests() <ipython-input-13-965b8ae2ab63> in doTests() 1 def doTests(): 2 # YOUR CODE HERE ----> 3 assert DecideTrade(1,100,0,10000)==['round=1&action=b&quantity=10&bidprice=50'] 4 assert DecideTrade(1,100,0,10000)==['round=59&action=s&quantity=5'] 5 assert DecideTrade(1,5,0,10000)==['round=test&action=b&quantity=2'] <ipython-input-6-a31be3b6a0cc> in DecideTrade(RoundNum, StockPrice, Shares, Money) 8 """ 9 # YOUR CODE HERE ---> 10 raise NotImplementedError() NotImplementedError:
Test your code from above here(1 point), ID: doTests_exists
In [ ]:
#check that doTests exists #also, at this point, doTests() shouldn't work because DecideTrade doesn't exist
Test your code from above here(1 point), ID: doTests_works
In [ ]:
#test doTests by giving a proper DecideTrade function and check that it passes
Test your code from above here(1 point), ID: doTests_has_tests
In [ ]:
#check that there are at least 3 reasonable additional assert statements inside doTests

Your next task is to complete the function DecideTrade. This is just the fixed algorithm that you have been given above. You will have the opportunity to design your own trading strategy later, but this will go in a function called CreativeTrading, not here.

Marks: 6
In [9]:
TEAMCODE="CBR" def DecideTrade(RoundNum,StockPrice,Shares,Money): """ RoundNum: The current round number StockPrice: the current stock price Shares: the number of shares your team owns Money: the amount of money your team has Returns a list of strings in a particular format (see notes) specifying a trade. """ s='s' b='b' bidprice=[] if StockPrice>=120: action = s quantity = Shares bidprice = StockPrice return'round='+str(RoundNum)+'&'+"action="+str(action)+'&'+"quantity="+str(quantity)+'&'+"bidprice="+str(bidprice) elif StockPrice<=80: action = b quantity = int(Money/StockPrice) bidprice = StockPrice return'round='+str(RoundNum)+'&'+"action="+str(action)+'&'+"quantity="+str(quantity)+'&'+"bidprice="+str(bidprice) else: action = s quantity =0 bidprice = StockPrice return'round='+str(RoundNum)+'&'+"action="+str(action)+'&'+"quantity="+str(quantity)+'&'+"bidprice="+str(bidprice) DecideTrade(2,104,4,1000)
'round=2&action=s&quantity=0&bidprice=104'
Test your code from above here(1 point), ID: unhidden_test
In [2]:
#first basic test, so you can see what the output is supposed to look like. if DecideTrade(1,100,0,10000)=='round=1&action=s&quantity=0': print("You have returned a string not a list of strings") assert DecideTrade(1,100,0,10000)==['round=1&action=s&quantity=0']
You have returned a string not a list of strings
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-2-5e6327143c9c> in <module> 2 if DecideTrade(1,100,0,10000)=='round=1&action=s&quantity=0': 3 print("You have returned a string not a list of strings") ----> 4 assert DecideTrade(1,100,0,10000)==['round=1&action=s&quantity=0'] AssertionError:
In [ ]:
Test your code from above here(1 point), ID: verify_doTests_as_specified
In [ ]:
#call DecideTrade and see if the answers yield the pre-defined values #if you have changed DecideTrade from what was requested, this will fail
In [ ]:
#verify that DecideTrade returns a LIST of strings
In [ ]:
# some edge cases

## Stock Market Simulation

We're now in a position to start running a stock market simulation. Hopefully over the duration of the simulation, you will introduce some improvements (see below). Certainly when we run a second stock market simulation in the second week, you should expect to have the additional elements of automated bid submission, automated status update, and a customised bidding algorithm up and running.

The first round will open for trading on Thursday at 9:20am, and close at 9:40pm, at which point round 2 will open. Round 2 closes at 10:00am. These two rounds have a lot of time associated with them, giving you the chance to step through manually what's happening in case there are any problems. Be ready to go, and hopefully it should all work. Then you can use your time to make sure the next two subsections are working as well. The rounds will decrease in duration. Ultimately, they'll be about 5 seconds apart each by the end of the Thursday session. The winning team will be the one with the largest value (cash+value of stocks using final stock price) at the end of the hundredth round.

In the second week, we will repeat the game with similar timings.

## Automated Bid Submission

Instead of filling in the form each time, you can get your code to submit the bid for you. To do this, you need to import a package:

import requests
Then you can open the contents of a website
url='http://something'
#open the webpage
response=requests.get(url)
#check that it opened correctly
assert response.status_code == requests.codes.ok
#display the contents of the website
print(response.text)

In particular, you're going to want to go to the website http://www.ma.rhul.ac.uk/akay/teaching/MMS/dotrade.php. This is the page that the 'Submit a Trade' form sends your data to. The only question is how to send the data. There are two standard methods of submitting form data, called GET and POST. We've chosen to use GET here -- it's easier but less secure. In the GET method, you add a question mark on the end of the address, and then add the data you want to submit. There are sets of parameter=value, each separated by the & symbol. So, a buy request would look like

1. Write a function SubmitTrade that accepts as input a single string corresponding to an entry in the list supplied by the output of DecideTrade. This will make use of the above code, but you need to modify it. Use it to compose the url and submit the trade. The function should return the text that is received in response to the submission (useful for an assert later!).

If you want to test your code without actually making a trade, you can supply a value of 'test' for round instead of a numerical value.

Marks: 2
In [1]:
Error 6: Supplied round number does not match current round number
In [ ]:
#check the the code is returning something sensible.
In [ ]:

## Automated Status Update

In a similar way, you can get an automated status update from

url="http://www.ma.rhul.ac.uk/akay/teaching/MMS/status.php?team=ABC"
Now, however, you'll need to process the output more carefully. We've already said that the data is in JSON format. The requests extension gives a way of accessing JSON files.
url="http://www.ma.rhul.ac.uk/akay/teaching/MMS/status.php?team=ABC"
response=requests.get(url)
#check that it opened correctly
assert response.status_code == requests.codes.ok
#process the json file
data=response.json()
data

Note that all values will be reported as text, not numbers, even if the answer is a number. To force Python to understand that it's a number, you can use the int() function.

In [ ]:
1. Write a function GetStatus that returns a list of the parameters round_number,share_price,stocks,cash (as a list) by opening the status page http://www.ma.rhul.ac.uk/akay/teaching/MMS/status.php specialised to your team.

Note that the outputs of GetStatus are just what DecideTrade needs as input. Hint: learn how to use the requests function json. This will give you a nested dictionary structure from which you will need to extract the information you want and convert it into a list in the correct order.

Marks: 4
In [5]:
import json import requests def GetStatus(): """ no inputs returns a list of the current round number, share price, team's stock and team's cash, as supplied by the website. """ url="http://www.ma.rhul.ac.uk/akay/teaching/MMS/status.php?team=CBR" response=requests.get(url) data=response.json() return(data['Status']) print(GetStatus())
{'Round': '1', 'Mode': '0', 'SharePrice': '100', 'Team Cash': '10000', 'Team Stocks': '0'}
Test your code from above here(2 points), ID: GetStatus_reutrns_list_integers
In [ ]:
#this verifies that the 4 elements of the output list from GetStatus are integers data=GetStatus() assert len(data)==4 [roundno,shareprice,stocks,cash]=data assert isinstance(roundno,int),"Round number should be an integer" assert isinstance(shareprice,int),"Share price should be an integer" assert isinstance(stocks,int),"Number of stocks should be an integer" assert isinstance(cash,int),"Cash should be an integer"
Test your code from above here(2 points), ID: GetStatus_returns_correct_vals
In [ ]:
#now check the actual values

## Bringing it all Together

The sequence GetStatus$\rightarrow$ DecideTrade $\rightarrow$SubmitTrade should now be able to automatically do a single round of trade. (What are the inputs and outputs of each of the 3 functions? How should they match up? None of them match up perfectly, and require a little bit of conversion.)

1. Create a function DoSingleRound that accepts a single parameter CurrentRound. Use this information about the current round so that it gets the current status, and if moves are being accepted for a round that is greater than CurrentRound, a move should be decided and submitted. The function should return the current round number according to the status message from the server.

2. Can you adapt your code to repeatedly check the status, and whenever a new round starts, you execute the code automatically? Implement this as a function ConstantRun. This should run until the end of the game (after 100 rounds), and automatically submit your bids without any intervention. (While you will obviouslly call ConstantRun, when you submit your code, please delete all calls to the function.)

For the second run of the stock market simulation (i.e. in week 3), it will be checked that you submitted bids for most rounds.

Warning! The webserver that we're using is not really set up for this sort of trading. If you try to submit too many trades in a short period of time, it will throw an error saying that you don't have permission to access certain resources. To avoid this, it's worth putting a gap of about 100ms after each requests.get call. This is done by importing the time module, and using the sleep command.

import time

print("hello") #do something
time.sleep(5) #wait 5 seconds
print("hello again") #do something else
Marks: 6

## Get Creative!

1. Finally, you can write your own algorithm within CreativeTrading for deciding your own trades. Do not change the existing DecideTrade function. Instead, replace the current return statement in the code stub below for CreativeTrading. The input and output format should be identical to that of DecideTrade. Make sure that, for every possible set of inputs, your function returns a valid trade.

If you want CreativeTrading to accept more parameters than DecideTrade, make sure that they are optional parameters, listed after the originals, and that you code returns a valid trade when those optional parameters are not supplied.

Now you're free to use these basic building blocks however you wish in order to maximise your chances of winning (although you may not actively collude with other teams). Remember that speed helps! You may import any functions that you want, such as randomness. What strategies do you think other teams might be using? Can you second-guess that strategy and find a way to make money from it? Is there any analysis of the whole process that you might perform in order to predict what might be optimal moves for the first or last rounds?

If you're feeling really brave, you may have noticed that there's one extra link on the webpage. That takes you to a .csv file that contains a history of all the rounds. Perhaps you want to download and import that data, and use it to perform some sort of time series analysis or similar?

There is the potential for your CreativeTrading function to become very complicated. Start simple, make sure it's working (don't forget your tests!), and only start to make it more sophisticated later when you know how much time you have available.

Marks: 4
In [6]:
File "<ipython-input-6-ee34859fb822>", line 28 return'round='+str(RoundNum)+'&'+"action="+str(action)+'&'+"quantity="+str(quantity)+'&'+"bidprice="+str(bidprice) ^ SyntaxError: invalid syntax
Test your code from above here(1 point), ID: DoSingleRound_good_output
In [ ]:
#does DoSingleRound give the required output?
Test your code from above here(1 point), ID: ConstantRun_uses_DoSingleRound
In [ ]:
#check that ConstantRun makes use of DoSingleRound
Test your code from above here(1 point), ID: DoSingleRound_uses_other_funcs
In [ ]:
#check the structure of DoSingleRound
In [ ]:
#now test the creative trading function
In [ ]:
#now test the creative trading function #a typical middle of the road case
In [ ]:
#now test the creative trading function #some extremal cases
In [ ]:
#now test the creative trading function
Test your code from above here(3 points), ID: Verify_participation
In [ ]:
#check that moves were made for at least 70% of rounds in the game (3 marks)

Describe (in your own words) how you altered your CreativeTrading function in light of any feedback you received via Moodle, and your understanding of how other algorithms worked. Each member of the pair programming pair should write their own explanation, independently, in a separate copy of the file (which will contain the same solutions to the programming questions).

Marks: 5

General feedback on your code structure, style, commenting, tests, explanations etc. You do not have to do anything here.

Some credit will be given for the ambition of the CreativeTrading function.

Marks: 5

## Stock Model

• Sales are always successful, and are performed directly with the bank.
• Sale price is determined at the end as the lower of the share price at the start of the round and the end of the round.
• Purchase requests are ordered by bid price, then with a tie-breaker of who placed the order first (speed is everything!), and are fulfilled until either (a) all are complete; (b) all available shares are sold.
• Newly available shares will all be sold. Any shares held by the bank will be sold after those, and only if they can be sold for at least the current stock price.

The new stock price is determined according to the logic:

if nobody was selling (except bank), but there were buyers:
new stock price=min(max(largest bid,old stock price),old stock price*3/2)
if nobody was buying, but there were sellers:
new stock price=max(old stock price*3/4,1)
if everything sold (sellers and bank) and there was a bid of at least 75\% of the stock price:
new stock price=max(largest bid, 1.5*average sale price)
if everything sold (sellers and bank) but there were no serious buyers:
new stock price=max(1,min(largest bid*1.2,1.5*average bid price,1.4*upper quartile of bids))
if not all sellers' stock sold:
new stock price=max(min(average sale price,0.9*old stock price),1)
if all seller's stock sold, but not all bank's:
new stock price=upper quartile of sales price

If a purchase order is made and your team does not have enough money to cover it (and all previous purchase orders for that round), the order is invalid, and no further orders can be placed that round. If a sell request is made, if it is for more than the stock owned, it is invalid, and nothing further can be done that round.

In [ ]:
#ignore this, it's something that helps styling the notebook. from IPython.core.display import HTML def css_styling(): styles = open("custom.css", "r").read() return HTML(styles) css_styling()
In [ ]: