Advertisement
Guest User

PredictIt.py

a guest
Mar 4th, 2018
189
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.09 KB | None | 0 0
  1. import ast
  2. import datetime
  3. from time import sleep
  4. from urllib.request import urlopen
  5. import mechanicalsoup
  6. import re
  7. from bs4 import BeautifulSoup
  8. import requests
  9.  
  10.  
  11. def chunks(l, n):
  12. for i in range(0, len(l), n):
  13. yield l[i:i + n]
  14.  
  15.  
  16. def floatify(string):
  17. """
  18. Converts a string representing cents into an orderable float.
  19. :param string: str
  20. :return:
  21. """
  22. temporary_string = f"0.{string[:-1]}"
  23. return float(temporary_string)
  24.  
  25.  
  26. class Contract:
  27. """
  28. Contains data and methods for an individual contract. For any contract, you can check the latest
  29. pricing and volume data. Additionally, with a logged in API session, you can check your own
  30. gains, losses, potential gains/losses depending on resolution, and more!
  31. """
  32. def __init__(self, market, cid, name, type_, shares, avg_price, buy_offers,
  33. sell_offers, gain_loss, latest, buy, sell, ticker):
  34. self.timestamp = datetime.datetime.now()
  35. self.market = market
  36. self.cid = cid
  37. self.name = name
  38. if self.market == self.name:
  39. self.name = type_.title()
  40. self.type_ = type_
  41. self.number_of_shares = int(shares)
  42. self.avg_price = avg_price
  43. self.buy_offers = buy_offers
  44. self.sell_offers = sell_offers
  45. self.gain_loss = gain_loss
  46. if '(' in self.gain_loss:
  47. self.gain_loss = gain_loss.replace('(', '-').replace(')', '')
  48. self.latest = latest
  49. self.buy = buy
  50. self.sell = sell
  51. self.ticker = ticker
  52. self.latest_volume = None
  53.  
  54. @property
  55. def shares(self):
  56. if self.number_of_shares == 1:
  57. return f"You have {self.number_of_shares} {self.type_} share."
  58. else:
  59. return f"You have {self.number_of_shares} {self.type_} shares."
  60.  
  61. @property
  62. def average_price(self):
  63. return f"Your average purchase price of {self.type_} shares is {self.avg_price}"
  64.  
  65. @property
  66. def gain_or_loss(self):
  67. if '+' in self.gain_loss:
  68. return f"Your shares have gained {self.gain_loss} in value."
  69. else:
  70. return f"Your shares have lost {self.gain_loss} in value."
  71.  
  72. @property
  73. def sell_price(self):
  74. return f"{self.type_} shares are currently being bought for {self.sell}"
  75.  
  76. @property
  77. def buy_price(self):
  78. return f"{self.type_} shares are currently being sold at {self.buy}"
  79.  
  80. @property
  81. def estimate_sale_of_current_shares(self):
  82. try:
  83. if float(self.sell[:-1]) - float(self.avg_price[:-1]) > 0:
  84. return f"If you sold all of your shares now, you would earn ${str(float(float(self.sell[:-1]) - float(self.avg_price[:-1])) * self.number_of_shares * 0.01)}"
  85. else:
  86. return f"If you sold all of your shares now, you would lose ${str(float(float(self.sell[:-1]) - float(self.avg_price[:-1])) * self.number_of_shares * 0.01 + (float(self.number_of_shares) * float(self.avg_price[:-1])) * 0.01)}"
  87. except ValueError as e:
  88. return e
  89.  
  90. @property
  91. def estimate_best_result(self):
  92. return f"If this contract resolves to {self.type_}, you would earn ${1 - (float(self.avg_price[:1])) * self.number_of_shares * 0.01 * -1}. Otherwise, you would lose ${float(self.avg_price[:1]) * self.number_of_shares * 0.01 * -1 - (float(self.number_of_shares) * float(self.avg_price[:-1])) * 0.01}}}"
  93.  
  94. @property
  95. def implied_odds(self):
  96. """Implied odds of a contract are what a given resolution
  97. in a market is being bought for currently."""
  98. return f"The implied odds of this contract resolving to {self.type_} are {self.buy.replace('¢', '%')}"
  99.  
  100. @property
  101. def volume(self):
  102. return f"There have been {self.latest_volume} shares traded today."
  103.  
  104. def summary(self):
  105. print('----')
  106. print(self.timestamp)
  107. print(self.market)
  108. print(self.name)
  109. print(self.shares)
  110. print(self.gain_or_loss)
  111. print(self.average_price)
  112. print(self.buy_price)
  113. print(self.sell_price)
  114. print(self.estimate_sale_of_current_shares)
  115. print(self.implied_odds)
  116. print(self.estimate_best_result)
  117. print('-----')
  118.  
  119. def get_current_volume(self):
  120. """
  121. Sets or updates volume. Volume data is only updated hourly, so use accordingly.
  122. """
  123. latest_data = ast.literal_eval(
  124. urlopen(
  125. f'https://www.predictit.org/PublicData/GetChartData?contractIds={self.cid}&timespan=24H').read().decode(
  126. 'utf-8').replace(
  127. 'false', 'False').replace('true', 'True').replace('null', 'None'))[-1]
  128. self.latest_volume = latest_data['TradeVolume']
  129. return
  130.  
  131. def buy_shares(self, api, number_of_shares, buy_price):
  132. if self.type_.lower() == 'no' or 'short':
  133. type_, id_ = 'Short', '0'
  134. elif self.type_.lower() == 'yes' or 'long':
  135. type_, id_ = 'Long', '1'
  136. load_side_page = api.browser.get(f'https://www.predictit.org/Trade/LoadBuy{type_}?contractId={self.cid}')
  137. token = load_side_page.soup.find('input', attrs={'name': '__RequestVerificationToken'}).get('value')
  138. r = api.browser.post('https://www.predictit.org/Trade/SubmitTrade',
  139. {'__RequestVerificationToken': token,
  140. 'BuySellViewModel.ContractId': self.cid,
  141. 'BuySellViewModel.TradeType': id_,
  142. 'BuySellViewModel.Quantity': number_of_shares,
  143. 'BuySellViewModel.PricePerShare': f'{float(buy_price)}',
  144. 'X-Requested-With': 'XMLHttpRequest'})
  145. if str(r.status_code) == '200':
  146. if 'Confirmation Pending' in str(r.content):
  147. print('Purchase offer successful!')
  148. elif 'You do not have sufficient funds to make this offer' in str(r.content):
  149. print('You do not have sufficient funds to make this offer!')
  150. elif 'There was a problem creating your offer' in str(r.content):
  151. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  152. print('There was a problem creating the offer. Check to make sure that you don\'t have any \'yes\' contracts that would prevent you from buying \'no\'s or vice versa.')
  153. else:
  154. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  155. else:
  156. print(f"Request returned an invalid {r.status_code} code. Please make sure you're using valid login credentials.")
  157.  
  158. def buy_shares_tweet(self, api, number_of_shares, buy_price, yn):
  159. #if self.type_.lower() == 'no' or 'short':
  160. # type_, id_ = 'Short', '0'
  161. #elif self.type_.lower() == 'yes' or 'long':
  162. # type_, id_ = 'Long', '1'
  163. if yn == 'n':
  164. type_, id_ = 'Short', '0'
  165.  
  166. if yn == 'y':
  167. type_, id_ = 'Long', '1'
  168.  
  169. load_side_page = api.browser.get(f'https://www.predictit.org/Trade/LoadBuy{type_}?contractId={self.cid}')
  170. token = load_side_page.soup.find('input', attrs={'name': '__RequestVerificationToken'}).get('value')
  171. r = api.browser.post('https://www.predictit.org/Trade/SubmitTrade',
  172. {'__RequestVerificationToken': token,
  173. 'BuySellViewModel.ContractId': self.cid,
  174. 'BuySellViewModel.TradeType': id_,
  175. 'BuySellViewModel.Quantity': number_of_shares,
  176. 'BuySellViewModel.PricePerShare': f'{float(buy_price)}',
  177. 'X-Requested-With': 'XMLHttpRequest'})
  178. if str(r.status_code) == '200':
  179. if 'Confirmation Pending' in str(r.content):
  180. print('Purchase offer successful!')
  181. elif 'You do not have sufficient funds to make this offer' in str(r.content):
  182. print('You do not have sufficient funds to make this offer!')
  183. elif 'There was a problem creating your offer' in str(r.content):
  184. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  185. print(
  186. 'There was a problem creating the offer. Check to make sure that you don\'t have any \'yes\' contracts that would prevent you from buying \'no\'s or vice versa.')
  187. else:
  188. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  189. else:
  190. print(
  191. f"Request returned an invalid {r.status_code} code. Please make sure you're using valid login credentials.")
  192.  
  193. def sell_shares(self, api, number_of_shares, sell_price):
  194. if self.type_.lower() == 'no':
  195. type_, id_ = 'Short', '2'
  196. elif self.type_.lower() == 'yes':
  197. type_, id_ = 'Long', '3'
  198. print((f'https://www.predictit.org/Trade/LoadSell{type_}?contractId={self.cid}'))
  199. load_side_page = api.browser.get(f'https://www.predictit.org/Trade/LoadSell{type_}?contractId={self.cid}')
  200. token = load_side_page.soup.find('input', attrs={'name': '__RequestVerificationToken'}).get('value')
  201. r = api.browser.post('https://www.predictit.org/Trade/SubmitTrade',
  202. {'__RequestVerificationToken': token,
  203. 'BuySellViewModel.ContractId': self.cid,
  204. 'BuySellViewModel.TradeType': id_,
  205. 'BuySellViewModel.Quantity': number_of_shares,
  206. 'BuySellViewModel.PricePerShare': f'{float(sell_price)}',
  207. 'X-Requested-With': 'XMLHttpRequest'})
  208. if str(r.status_code) == '200':
  209. if 'Confirmation Pending' in str(r.content):
  210. print('Sell offer successful!')
  211. elif 'There was a problem creating your offer':
  212. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  213. print('There was a problem creating the offer. Check to make sure that you don\'t have any \'yes\' contracts that would prevent you from buying \'no\'s or vice versa.')
  214. else:
  215. print(f"DEBUGGING INFO - INCLUDE IN GITHUB ISSUE: {r.content}")
  216. print(r.content)
  217. else:
  218. print(f"Request returned an invalid {r.status_code} code. Please make sure you're using valid login credentials.")
  219.  
  220. def update(self):
  221. r = ast.literal_eval(
  222. urlopen(
  223. f'https://www.predictit.org/api/marketdata/ticker/{self.ticker}').read().decode(
  224. 'utf-8').replace(
  225. 'false', 'False').replace('true', 'True').replace('null', 'None'))
  226. for contract in r['Contracts']:
  227. if contract['TickerSymbol'] == self.ticker:
  228. if self.type_.lower() in ['yes', 'long']:
  229. self.buy = contract['BestBuyYesCost']
  230. elif self.type_.lower() in ['no', 'short']:
  231. self.buy = contract['BestBuyNoCost']
  232. elif self.type_.lower() in ['yes', 'long']:
  233. self.sell = contract['BestSellYesCost']
  234. elif self.type_.lower() in ['no', 'short']:
  235. self.sell = contract['BestSellNoCost']
  236.  
  237. def __str__(self):
  238. return f"{self.market}, {self.name}, {self.type_}, {self.shares}, {self.average_price}, {self.buy_offers},{self.sell_offers}, {self.gain_loss}, {self.latest}, {self.buy}, {self.sell}"
  239.  
  240.  
  241. class pyredictit:
  242. """
  243. This class provides access to the API and the methods below. You have to create an authed session
  244. with a valid email and password to use account-specific methods (buying, selling, etc.), but not
  245. to look up current share data.
  246. """
  247. def __init__(self):
  248. self.my_contracts = None
  249. self.gain_loss = None
  250. self.available = None
  251. self.invested = None
  252. self.browser = mechanicalsoup.Browser()
  253. self.browser.session.headers.update({
  254. 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.100 Safari/537.36'}
  255. )
  256.  
  257. def update_balances(self):
  258. user_funds = self.browser.get('https://www.predictit.org/PrivateData/_UserFundsMainNav').json()
  259. self.available = user_funds['BalanceText']
  260. self.gain_loss = user_funds['SharesText']
  261. self.invested = user_funds['PortfolioText']
  262.  
  263. def money_available(self):
  264. self.update_balances()
  265. print(f"You have {self.available} available.")
  266.  
  267. def current_gain_loss(self):
  268. self.update_balances()
  269. if '-' in self.gain_loss:
  270. print(f"You've lost {self.gain_loss[1:]}.")
  271. else:
  272. print(f"You've gained {self.gain_loss[1:]}.")
  273.  
  274. def money_invested(self):
  275. self.update_balances()
  276. print(f"You have {self.invested} currently invested in contracts.")
  277.  
  278. def create_authed_session(self, username, password):
  279. """
  280. Authenticates a given user.
  281. :type username: str
  282. :type password: str
  283. """
  284. login_page = self.browser.get('https://www.predictit.org/')
  285. login_form = login_page.soup.find('form', id='loginForm')
  286. login_form.select('#Email')[0]['value'] = username
  287. login_form.select('#Password')[0]['value'] = password
  288. self.browser.submit(login_form, login_page.url)
  289. return self.browser
  290.  
  291. def get_my_contracts(self):
  292. """
  293. Initially retrieves info for currently authed user's contracts.
  294. """
  295. self.my_contracts = []
  296. my_shares = self.browser.get('https://www.predictit.org/Profile/GetSharesAjax')
  297. for market in my_shares.soup.find_all('table', class_='table table-striped table-center'):
  298. market_title = market.previous_element.previous_element.find('div', class_='outcome-title').find('a').get(
  299. 'title')
  300. market_data = [i.text.strip().replace(
  301. "\n", "").replace(" ", "").replace('\r', '') for i in market.find_all('td')]
  302. market_data_lists = [market_data[x:x + 10] for x in range(0, len(market_data), 10)]
  303. cid = None
  304. for list_ in market_data_lists:
  305. parsed_market_data = [market_title]
  306. for string in list_:
  307. try:
  308. cid = re.search(
  309. pattern='#\w+\-(\d+)', string=string
  310. ).group(1)
  311. string = re.search(
  312. pattern='(.*)\$\(.*\)\;', string=string
  313. ).group(1)
  314. except AttributeError:
  315. pass
  316. parsed_market_data.append(string)
  317. for line in urlopen(f'https://www.predictit.org/Contract/{cid}/#data').read().splitlines():
  318. if 'ChartTicker' in str(line):
  319. ticker = re.search(pattern="= '(.*)';", string=str(line)).group(1)
  320. break
  321. parsed_market_data.insert(1, cid)
  322. parsed_market_data.append(ticker)
  323. contract = Contract(*parsed_market_data)
  324. self.my_contracts.append(contract)
  325.  
  326. def update_my_contracts(self):
  327. """
  328. Updates info on contracts currently held by the authenticated user.
  329. """
  330. my_shares = self.browser.get('https://www.predictit.org/Profile/GetSharesAjax')
  331. for market in my_shares.soup.find_all('table', class_='table table-striped table-center'):
  332. market_title = market.previous_element.previous_element.find('div', class_='outcome-title').find('a').get(
  333. 'title')
  334. for contract in self.my_contracts:
  335. if market_title == contract.market:
  336. market_data = [i.text.strip().replace(
  337. "\n", "").replace(" ", "").replace('\r', '') for i in market.find_all('td')]
  338. market_data_lists = [market_data[x:x + 10] for x in range(0, len(market_data), 10)]
  339. cid = None
  340. for list_ in market_data_lists:
  341. parsed_market_data = [market_title]
  342. for string in list_:
  343. try:
  344. cid = re.search(
  345. pattern='#\w+\-(\d+)', string=string
  346. ).group(1)
  347. string = re.search(
  348. pattern='(.*)\$\(.*\)\;', string=string
  349. ).group(1)
  350. except AttributeError:
  351. pass
  352. parsed_market_data.append(string)
  353. parsed_market_data.insert(1, cid)
  354. self.timestamp = datetime.datetime.now()
  355. self.avg_price = parsed_market_data[5]
  356. self.gain_loss = parsed_market_data[8]
  357. self.latest = parsed_market_data[9]
  358. self.buy = parsed_market_data[-2]
  359. self.sell = parsed_market_data[-1]
  360. else:
  361. continue
  362.  
  363. def list_my_contracts(self):
  364. """
  365. Provides a quick summary of currently held contracts.
  366. """
  367. self.get_my_contracts()
  368. try:
  369. for contract in self.my_contracts:
  370. print('------')
  371. print(contract.timestamp)
  372. print(contract.market)
  373. print(contract.ticker)
  374. print(contract.shares)
  375. print(contract.gain_or_loss)
  376. print(contract.average_price)
  377. print(contract.buy_price)
  378. print(contract.sell_price)
  379. print(contract.estimate_sale_of_current_shares)
  380. print(contract.implied_odds)
  381. print(contract.estimate_best_result)
  382. print('------')
  383. except TypeError:
  384. print('You don\'t have any active contracts!')
  385. return
  386.  
  387. def search_for_contracts(self, market, buy_sell, type_, contracts=None):
  388. """
  389. Search for contracts that aren't currently owned and add them to
  390. the contracts list, which is created if it isn't supplied.
  391. :param market: dict
  392. :param buy_sell: str
  393. :param type_: str
  394. :param contracts: list
  395. :return: list, contracts
  396. """
  397. if not contracts:
  398. contracts = []
  399. if type_.lower() in ['yes', 'long'] and buy_sell == 'buy':
  400. type_ = {'long': 'BestBuyYesCost'}
  401. elif type_.lower() in ['no', 'short'] and buy_sell == 'buy':
  402. type_ = {'short': 'BestBuyNoCost'}
  403. elif type_.lower() in ['yes', 'long'] and buy_sell == 'sell':
  404. type_ = {'long': 'BestSellYesCost'}
  405. elif type_.lower() in ['no', 'short'] and buy_sell == 'sell':
  406. type_ = {'short': 'BestSellNoCost'}
  407. if 'us' and 'election' in market.replace('.', '').lower():
  408. market_link = 'https://www.predictit.org/api/marketdata/category/6'
  409. elif 'us' and 'politic' in market.replace('.', '').lower():
  410. market_link = 'https://www.predictit.org/api/marketdata/category/13'
  411. elif 'world' in market.lower():
  412. market_link = 'https://www.predictit.org/api/marketdata/category/4'
  413. else:
  414. print('Invalid market selected.')
  415. return
  416. raw_market_data = self.browser.get(market_link).json()['Markets']
  417. for market in raw_market_data:
  418. for contract in market['Contracts']:
  419. if list(type_.keys())[0].title() == 'Long' and buy_sell == 'sell':
  420. new_contract = Contract(type_='long', sell=contract[list(type_.values())[0]], buy='0.00',
  421. buy_offers=0, sell_offers=0, avg_price='0.00', gain_loss='0.00',
  422. latest=contract['LastTradePrice'], market=market['Name'],
  423. name=contract['Name'], shares='0', cid=contract['ID'],
  424. ticker=contract['TickerSymbol']
  425. )
  426. contracts.append(new_contract)
  427. elif list(type_.keys())[0].title() == 'Short' and buy_sell == 'sell':
  428. new_contract = Contract(type_='short', sell=contract[list(type_.values())[0]], buy='0.00',
  429. buy_offers=0, sell_offers=0, avg_price='0.00', gain_loss='0.00',
  430. latest=contract['LastTradePrice'], market=market['Name'],
  431. name=contract['Name'], shares='0', cid=contract['ID'],
  432. ticker=contract['TickerSymbol']
  433. )
  434. contracts.append(new_contract)
  435. elif list(type_.keys())[0].title() == 'Long' and buy_sell == 'buy':
  436. new_contract = Contract(type_='long', sell='0.00', buy=contract[list(type_.values())[0]],
  437. buy_offers=0, sell_offers=0, avg_price='0.00', gain_loss='0.00',
  438. latest=contract['LastTradePrice'], market=market['Name'],
  439. name=contract['Name'], shares='0', cid=contract['ID'],
  440. ticker=contract['TickerSymbol']
  441. )
  442. contracts.append(new_contract)
  443. elif list(type_.keys())[0].title() == 'Short' and buy_sell == 'buy':
  444. new_contract = Contract(type_='short', sell='0.00', buy=contract[list(type_.values())[0]],
  445. buy_offers=0, sell_offers=0, avg_price='0.00', gain_loss='0.00',
  446. latest=contract['LastTradePrice'], market=market['Name'],
  447. name=contract['Name'], shares='0', cid=contract['ID'],
  448. ticker=contract['TickerSymbol']
  449. )
  450. contracts.append(new_contract)
  451. return contracts
  452.  
  453. def trigger_stop_loss(self, contract, number_of_shares, trigger_price):
  454. contract.sell_shares(api=self, number_of_shares=number_of_shares, sell_price=trigger_price)
  455.  
  456. def monitor_price_of_contract(self, contract, trigger_price, monitor_type, number_of_shares=None):
  457. """
  458. :param contract: Contract
  459. :param trigger_price: float
  460. :param monitor_type: str
  461. :param number_of_shares: int
  462. :return:
  463. """
  464. contract.update()
  465. if monitor_type == 'stop_loss':
  466. if floatify(contract.latest) <= trigger_price:
  467. contract.sell_shares(api=self, number_of_shares=number_of_shares, sell_price=trigger_price)
  468. else:
  469. print(f'Your sell price is {trigger_price}. The current price is {floatify(contract.latest)}')
  470. elif monitor_type == 'buy_at':
  471. if floatify(contract.latest) <= trigger_price:
  472. contract.buy_shares(api=self, number_of_shares=number_of_shares, buy_price=trigger_price)
  473. else:
  474. print(f'Your buy in price is {trigger_price}. The current price is {floatify(contract.latest)}')
  475. elif monitor_type == 'generic':
  476. print(contract.latest)
  477.  
  478. def set_stop_loss(self, contract, stop_loss, number_of_shares):
  479. """
  480. :param contract: Contract
  481. :param stop_loss: float
  482. :param number_of_shares: int
  483. :return:
  484. """
  485. while True:
  486. sleep(2)
  487. self.monitor_price_of_contract(contract, monitor_type='stop_loss',
  488. number_of_shares=number_of_shares, trigger_price=stop_loss)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement