def update_stock_prices_to_trend(self, api): ''' This function updates all prices in the user's stock to TREND. ''' uploadable_json = [] if os.path.isfile(PRICE_CHANGES_FILE): if PyMkmHelper.prompt_bool( "Found existing changes. Upload [y] or discard [n]?" ) == True: with open(PRICE_CHANGES_FILE, 'r') as changes: uploadable_json = json.load(changes) else: os.remove(PRICE_CHANGES_FILE) self.update_stock_prices_to_trend(api=self.api) else: uploadable_json = self.calculate_new_prices_for_stock(api=self.api) if len(uploadable_json) > 0: self.display_price_changes_table(uploadable_json) if PyMkmHelper.prompt_bool( "Do you want to update these prices?") == True: # Update articles on MKM api.set_stock(uploadable_json) print('Prices updated.') else: with open(PRICE_CHANGES_FILE, 'w') as outfile: json.dump(uploadable_json, outfile) print('Prices not updated. Changes saved.') else: print('No prices to update.')
def get_price_for_product(self, product_id, rarity, is_foil, language_id=1, undercut_local_market=False, api=None): r = api.get_product(product_id) rounding_limit = self.get_rounding_limit_for_rarity(rarity) if not is_foil: trend_price = r['product']['priceGuide']['TREND'] else: trend_price = r['product']['priceGuide']['TRENDFOIL'] # Set competitive price for region if undercut_local_market: table_data_local, table_data = self.get_competition( api, product_id, is_foil) if len(table_data_local) > 0: # Undercut if there is local competition lowest_in_country = PyMkmHelper.round_down_to_limit(rounding_limit, PyMkmHelper.calculate_lowest(table_data_local, 4)) new_price = max(rounding_limit, min( trend_price, lowest_in_country - rounding_limit)) else: # No competition in our country, set price a bit higher. new_price = PyMkmHelper.round_up_to_limit( rounding_limit, trend_price * 1.2) else: new_price = PyMkmHelper.round_up_to_limit( rounding_limit, trend_price) if new_price == None: raise ValueError('No price found!') else: return new_price
def list_competition_for_product(self, api): self.report("list competition for product") search_string = PyMkmHelper.prompt_string('Search card name') is_foil = PyMkmHelper.prompt_bool("Foil?") result = api.find_product(search_string, **{ # 'exact ': 'true', 'idGame': 1, 'idLanguage': 1, # TODO: Add Partial Content support # TODO: Add language support }) if (result): products = result['product'] stock_list_products = [x['idProduct'] for x in self.get_stock_as_array(api=self.api)] products = [x for x in products if x['idProduct'] in stock_list_products] if len(products) == 0: print('No matching cards in stock.') else: if len(products) > 1: product = self.select_from_list_of_products( [i for i in products if i['categoryName'] == 'Magic Single']) elif len(products) == 1: product = products[0] self.show_competition_for_product( product['idProduct'], product['enName'], is_foil, api=self.api) else: print('No results found.')
def get_price_for_product(self, product_id, rarity, is_foil, is_playset, language_id=1, undercut_local_market=False, api=None): try: response = api.get_product(product_id) except Exception as err: print('No response from API.') sys.exit(0) else: rounding_limit = self.get_rounding_limit_for_rarity(rarity) if response: if not is_foil: trend_price = response['product']['priceGuide']['TREND'] else: trend_price = response['product']['priceGuide'][ 'TRENDFOIL'] # Set competitive price for region if undercut_local_market and not is_playset: # FIXME: add support for playsets? table_data_local, table_data = self.get_competition( api, product_id, is_foil) if len(table_data_local) > 0: # Undercut if there is local competition lowest_in_country = PyMkmHelper.round_down_to_limit( rounding_limit, PyMkmHelper.calculate_lowest(table_data_local, 4)) new_price = max( rounding_limit, min(trend_price, lowest_in_country - rounding_limit)) else: # No competition in our country, set price a bit higher. new_price = PyMkmHelper.round_up_to_limit( rounding_limit, trend_price * 1.2) else: new_price = PyMkmHelper.round_up_to_limit( rounding_limit, trend_price) if new_price == None: raise ValueError('No price found!') else: if is_playset: new_price = 4 * new_price return new_price else: print('No results.')
def start(self): while True: menu_items = [ "Update stock prices", "Update price for a card", "List competition for a card", "Show top 20 expensive items in stock", "Show account info", "Clear entire stock (WARNING)", "Import stock from .\list.csv" ] self.print_menu(menu_items, f"PyMKM {__version__}") choice = input("Action number: ") try: if choice == "1": self.update_stock_prices_to_trend(api=self.api) elif choice == "2": search_string = PyMkmHelper.prompt_string( 'Search card name') self.update_product_to_trend(search_string, api=self.api) elif choice == "3": is_foil = False search_string = PyMkmHelper.prompt_string( 'Search card name') if PyMkmHelper.prompt_bool("Foil?") == True: is_foil = True self.list_competition_for_product(search_string, is_foil, api=self.api) elif choice == "4": self.show_top_expensive_articles_in_stock(20, api=self.api) elif choice == "5": self.show_account_info(api=self.api) elif choice == "6": self.clear_entire_stock(api=self.api) elif choice == "7": self.import_from_csv(api=self.api) elif choice == "0": break else: print("Not a valid choice, try again.") except ConnectionError as err: print(err)
def print_product_top_list(self, title_string, table_data, sort_column, rows): print(70*'-') print('{} \n'.format(title_string)) print(tb.tabulate(sorted(table_data, key=lambda x: x[sort_column], reverse=False)[:rows], headers=['Username', 'Country', 'Condition', 'Count', 'Price'], tablefmt="simple")) print(70*'-') print('Total average price: {}, Total median price: {}, Total # of articles: {}\n'.format( str(PyMkmHelper.calculate_average(table_data, 3, 4)), str(PyMkmHelper.calculate_median(table_data, 3, 4)), str(len(table_data)) ) )
def update_product_to_trend(self, api): ''' This function updates one product in the user's stock to TREND. ''' self.report("update product price to trend") search_string = PyMkmHelper.prompt_string('Search product name') try: articles = api.find_stock_article(search_string, 1) except Exception as err: print(err) if len(articles) > 1: article = self.select_from_list_of_articles(articles) else: article = articles[0] found_string = f"Found: {article['product']['enName']}" if article['product'].get('expansion'): found_string += f"[{article['product'].get('expansion')}]." else: found_string += '.' print(found_string) undercut_local_market = PyMkmHelper.prompt_bool( 'Try to undercut local market? (slower, more requests)') r = self.get_article_with_updated_price(article, undercut_local_market, api=self.api) if r: self.draw_price_changes_table([r]) print('\nTotal price difference: {}.'.format( str( round( sum(item['price_diff'] * item['count'] for item in [r]), 2)))) if PyMkmHelper.prompt_bool( "Do you want to update these prices?") == True: # Update articles on MKM print('Updating prices...') api.set_stock([r]) print('Price updated.') else: print('Prices not updated.') else: print('No prices to update.')
def update_product_to_trend(self, search_string, api): ''' This function updates one product in the user's stock to TREND. ''' try: articles = api.find_stock_article(search_string, 1) except Exception as err: print(err) if len(articles) > 1: article = self.select_from_list_of_articles(articles) else: article = articles[0] print('Found: {} [{}].'.format(article['product']['enName'], article['product']['expansion'])) r = self.get_price_for_single_article(article, api=self.api) if r: self.draw_price_changes_table([r]) print('\nTotal price difference: {}.'.format( str( round( sum(item['price_diff'] * item['count'] for item in [r]), 2)))) if PyMkmHelper.prompt_bool( "Do you want to update these prices?") == True: # Update articles on MKM api.set_stock([r]) print('Price updated.') else: print('Prices not updated.') else: print('No prices to update.')
def get_foil_price(self, api, product_id, language_id): # NOTE: This is a rough algorithm, designed to be safe and not to sell aggressively. # 1) See filter parameters below. # 2) Set price to lowest + (median - lowest / 4), rounded to closest quarter Euro. # 3) Undercut price in own country if not contradicting 2) # 4) Never go below 0.25 for foils account = api.get_account()['account'] articles = api.get_articles( product_id, **{ 'isFoil': 'true', 'isAltered': 'false', 'isSigned': 'false', 'minCondition': 'GD', 'idLanguage': language_id }) keys = ['idArticle', 'count', 'price', 'condition', 'seller'] foil_articles = [{x: y for x, y in art.items() if x in keys} for art in articles] local_articles = [] for article in foil_articles: if article['seller']['address']['country'] == account[ 'country'] and article['seller']['username'] != account[ 'username']: local_articles.append(article) local_table_data = [] for article in local_articles: if article: local_table_data.append([ article['seller']['username'], article['seller']['address']['country'], article['condition'], article['count'], article['price'] ]) table_data = [] for article in foil_articles: if article: table_data.append([ article['seller']['username'], article['seller']['address']['country'], article['condition'], article['count'], article['price'] ]) median_price = PyMkmHelper.calculate_median(table_data, 3, 4) lowest_price = PyMkmHelper.calculate_lowest(table_data, 4) median_guided = PyMkmHelper.round_up_to_quarter( lowest_price + (median_price - lowest_price) / 4) if len(local_table_data) > 0: # Undercut if there is local competition lowest_in_country = PyMkmHelper.round_down_to_quarter( PyMkmHelper.calculate_lowest(local_table_data, 4)) return max(0.25, min(median_guided, lowest_in_country - 0.25)) else: # No competition in our country, set price a bit higher. return PyMkmHelper.round_up_to_quarter(median_guided * 1.2)
def update_stock_prices_to_trend(self, api): ''' This function updates all prices in the user's stock to TREND. ''' self.report("update stock price to trend") undercut_local_market = PyMkmHelper.prompt_bool( 'Try to undercut local market? (slower, more requests)') uploadable_json = self.calculate_new_prices_for_stock( undercut_local_market, api=self.api) if len(uploadable_json) > 0: self.display_price_changes_table(uploadable_json) if PyMkmHelper.prompt_bool("Do you want to update these prices?") == True: # Update articles on MKM api.set_stock(uploadable_json) print('Prices updated.') else: print('Prices not updated.') else: print('No prices to update.')
def clear_entire_stock(self, api): self.report("clear entire stock") stock_list = self.get_stock_as_array(api=self.api) if PyMkmHelper.prompt_bool("Do you REALLY want to clear your entire stock ({} items)?".format(len(stock_list))) == True: # for article in stock_list: # article['count'] = 0 delete_list = [{'count': x['count'], 'idArticle': x['idArticle']} for x in stock_list] api.delete_stock(delete_list) print('Stock cleared.') else: print('Aborted.')
def setUp(self): self.helper = PyMkmHelper()
class TestPyMkmHelperFunctions(unittest.TestCase): def setUp(self): self.helper = PyMkmHelper() def test_calculate_average(self): table = [['Yxskaft', 'SE', 'NM', 1, 1.21], ['Frazze11', 'SE', 'NM', 3, 1.3], ['andli826', 'SE', 'NM', 2, 1.82]] self.assertEqual(self.helper.calculate_average(table, 3, 4), 1.46) def test_calculate_median(self): table = [['Yxskaft', 'SE', 'NM', 1, 1.21], ['Frazze11', 'SE', 'NM', 3, 1.3], ['andli826', 'SE', 'NM', 2, 1.82]] self.assertEqual(self.helper.calculate_median(table, 3, 4), 1.3) self.assertEqual(self.helper.calculate_average(table, 3, 4), 1.46) def test_calculate_lowest(self): table = [['Yxskaft', 'SE', 'NM', 1, 1.21], ['Frazze11', 'SE', 'NM', 3, 1.3], ['andli826', 'SE', 'NM', 2, 1.82]] self.assertEqual(self.helper.calculate_lowest(table, 4), 1.21) def test_round_up_to_limit(self): self.assertEqual(self.helper.round_up_to_limit(0.25, 0.99), 1) self.assertEqual(self.helper.round_up_to_limit(0.25, 0), 0) self.assertEqual(self.helper.round_up_to_limit(0.25, 0.1), 0.25) self.assertEqual(self.helper.round_up_to_limit(0.1, 0.99), 1) self.assertEqual(self.helper.round_up_to_limit(0.01, 0.011), 0.02) self.assertEqual(self.helper.round_up_to_limit(0.01, 1), 1) self.assertEqual(self.helper.round_up_to_limit(1, 0.1), 1) def test_round_down_to_limit(self): self.assertEqual(self.helper.round_down_to_limit(0.25, 0.99), 0.75) self.assertEqual(self.helper.round_down_to_limit(0.25, 1.01), 1) self.assertEqual(self.helper.round_down_to_limit(0.25, 0.1), 0) self.assertEqual(self.helper.round_down_to_limit(0.1, 0.99), 0.9) self.assertEqual(self.helper.round_down_to_limit(0.01, 0.011), 0.01) self.assertEqual(self.helper.round_down_to_limit(0.01, 1), 1) self.assertEqual(self.helper.round_down_to_limit(1, 0.1), 0) @patch('sys.stdout', new_callable=io.StringIO) @patch('builtins.input', side_effect=['y', 'n', 'p', 'n']) def test_prompt_bool(self, mock_input, mock_stdout): self.assertTrue(self.helper.prompt_bool('test_y')) self.assertFalse(self.helper.prompt_bool('test_n')) self.helper.prompt_bool('test_error') self.assertRegex(mock_stdout.getvalue(), r'\nPlease answer with y\/n\n') @patch('builtins.input', side_effect=['y']) def test_prompt_string(self, mock_input): self.assertEqual(self.helper.prompt_string('test'), 'y')
def find_deals_from_user(self, api): self.report("find deals from user") search_string = PyMkmHelper.prompt_string('Enter username') try: result = api.find_user_articles(search_string) except NoResultsError as err: print(err.mkm_msg()) else: if (result): filtered_articles = [ x for x in result if x.get('condition') in PyMkmApi.conditions[:3] ] # EX+ sorted_articles = sorted(result, key=lambda x: x['price'], reverse=True) print( f"User '{search_string}' has {len(sorted_articles)} articles in stock." ) num_searches = int( PyMkmHelper.prompt_string( f'Searching top X expensive cards (EX+) for deals, choose X (1-{len(sorted_articles)})' )) if num_searches > 1 and num_searches <= len(sorted_articles): table_data = [] index = 0 bar = progressbar.ProgressBar(max_value=num_searches) for article in sorted_articles[:num_searches]: p = api.get_product(article['idProduct']) name = p['product']['enName'] expansion = p['product']['expansion']['enName'] condition = article.get('condition') language = article['language']['languageName'] foil = article['isFoil'] price = float(article['price']) if foil: market_price = p['product']['priceGuide'][ 'TRENDFOIL'] else: market_price = p['product']['priceGuide']['TREND'] price_diff = price - market_price if price_diff < 0: table_data.append([ name, expansion, condition, language, u'\u2713' if foil else '', price, market_price, price_diff ]) index += 1 bar.update(index) bar.finish() if table_data: print('Found some interesting prices:') print( tb.tabulate(sorted(table_data, key=lambda x: x[5], reverse=True), headers=[ 'Name', 'Expansion', 'Condition', 'Language', 'Foil?', 'Price', 'Market price', 'Market diff' ], tablefmt="simple")) else: print('Found no deals. :(') else: print("Invalid number.") else: print('No results found.')
def find_deals_from_user(self, api): self.report("find deals from user") search_string = PyMkmHelper.prompt_string('Enter username') try: result = api.find_user_articles(search_string) except NoResultsError as err: print(err.mkm_msg()) else: if (result): # [x for x in result if x.get('condition') in PyMkmApi.conditions[:3]] # EX+ filtered_articles = result # price > 1 filtered_articles = [x for x in result if x.get('price') > 1] sorted_articles = sorted(filtered_articles, key=lambda x: x['price'], reverse=True) print( f"User '{search_string}' has {len(sorted_articles)} articles that meet the criteria." ) num_searches = int( PyMkmHelper.prompt_string( f'Searching top X expensive cards for deals, choose X (1-{len(sorted_articles)})' )) if num_searches > 1 and num_searches <= len(sorted_articles): table_data = [] index = 0 bar = progressbar.ProgressBar(max_value=num_searches) for article in sorted_articles[:num_searches]: condition = article.get('condition') language = article.get('language').get('languageName') foil = article.get('isFoil') playset = article.get('isPlayset') price = float(article['price']) p = api.get_product(article['idProduct']) name = p['product']['enName'] expansion = p['product'].get('expansion') if expansion: expansion_name = expansion.get('enName') else: expansion_name = 'N/A' if foil: market_price = p['product']['priceGuide'][ 'TRENDFOIL'] else: market_price = p['product']['priceGuide']['TREND'] price_diff = price - market_price percent_deal = round(-100 * (price_diff / market_price)) if price_diff < -1 or percent_deal >= 10: table_data.append([ name, expansion_name, condition, language, u'\u2713' if foil else '', u'\u2713' if playset else '', price, market_price, price_diff, percent_deal ]) index += 1 bar.update(index) bar.finish() if table_data: print('Found some interesting prices:') print( tb.tabulate(sorted(table_data, key=lambda x: x[9], reverse=True), headers=[ 'Name', 'Expansion', 'Condition', 'Language', 'Foil', 'Playset', 'Price', 'Market price', 'Market diff', 'Deal %' ], tablefmt="simple")) else: print('Found no deals. :(') else: print("Invalid number.") else: print('No results found.')