def get_listings_with_other_rarity_tags( look_for_profile_backgrounds, retrieve_listings_with_another_rarity_tag_from_scratch=False): if retrieve_listings_with_another_rarity_tag_from_scratch: other_rarity_fields = set(get_rarity_fields()).difference({'common'}) for rarity_tag in other_rarity_fields: update_all_listings_for_items_other_than_cards(rarity=rarity_tag) if look_for_profile_backgrounds: all_listings_for_uncommon = load_all_listings( listing_output_file_name= get_listing_output_file_name_for_profile_backgrounds( rarity='uncommon')) all_listings_for_rare = load_all_listings( listing_output_file_name= get_listing_output_file_name_for_profile_backgrounds( rarity='rare')) else: all_listings_for_uncommon = load_all_listings( listing_output_file_name=get_listing_output_file_name_for_emoticons( rarity='uncommon')) all_listings_for_rare = load_all_listings( listing_output_file_name=get_listing_output_file_name_for_emoticons( rarity='rare')) return all_listings_for_uncommon, all_listings_for_rare
def filter_listings( all_listings=None, min_sell_price=30, # in cents min_num_listings=20, # to remove listings with very few sellers, who chose unrealistic sell prices verbose=True): if all_listings is None: all_listings = load_all_listings() # Sort listing hashes with respect to the ask sorted_listing_hashes = sorted(all_listings, reverse=True, key=lambda x: all_listings[x]['sell_price']) # *Heuristic* filtering of listing hashes filtered_listing_hashes = list( filter( lambda x: all_listings[x]['sell_price'] >= min_sell_price and all_listings[x]['sell_listings'] >= min_num_listings, sorted_listing_hashes)) if verbose: print('{} hashes found.\n'.format(len(filtered_listing_hashes))) return filtered_listing_hashes
def update_all_listing_details(listing_hashes=None, listing_details_output_file_name=None): # Caveat: this is mostly useful if download_all_listing_details() failed in the middle of the process, and you want # to restart the process without risking to lose anything, in case the process fails again. if listing_details_output_file_name is None: listing_details_output_file_name = get_listing_details_output_file_name( ) try: with open(listing_details_output_file_name, 'r', encoding='utf-8') as f: all_listing_details = json.load(f) print('Loading {} listing details from disk.'.format( len(all_listing_details))) except FileNotFoundError: print('Downloading listing details from scratch.') all_listing_details = None if listing_hashes is None: all_listings = load_all_listings() listing_hashes = list(all_listings.keys()) all_listing_details = get_listing_details_batch( listing_hashes, all_listing_details, save_to_disk=True, listing_details_output_file_name=listing_details_output_file_name) return all_listing_details
def get_sell_prices_without_fee(app_ids, price_offset_in_euros=0.0): # Load sell prices (without fee). # # NB: an arbitrary price offset (greater than or equal to zero) can be input to constrain the problem even more. # This is a security: if the price offset is positive (>0), then we know that we can under-cut the lowest sell order # and still be able to make a profit if someone agrees to buy from us. data = load_all_listings() sell_prices = dict() for listing_hash in data: app_id_as_int = convert_listing_hash_to_app_id(listing_hash) app_id = str(app_id_as_int) if app_id in app_ids: current_data = data[listing_hash] sell_price_in_cents = current_data['sell_price'] sell_price_in_euros = int(sell_price_in_cents) / 100 sell_price_after_arbitrary_offset = sell_price_in_euros - abs( price_offset_in_euros) sell_price_in_euros_without_fee = compute_sell_price_without_fee( sell_price_after_arbitrary_offset) sell_prices[app_id] = sell_price_in_euros_without_fee return sell_prices
def load_aggregated_badge_data(retrieve_listings_from_scratch=False, enforced_sack_of_gems_price=None, minimum_allowed_sack_of_gems_price=None, from_javascript=False): badge_creation_details = parse_badge_creation_details( from_javascript=from_javascript) if retrieve_listings_from_scratch: update_all_listings() all_listings = load_all_listings() all_listings = filter_out_dubious_listing_hashes(all_listings) badge_matches = match_badges_with_listing_hashes(badge_creation_details, all_listings) retrieve_gem_price_from_scratch = bool(enforced_sack_of_gems_price is None) aggregated_badge_data = aggregate_badge_data( badge_creation_details, badge_matches, all_listings=all_listings, enforced_sack_of_gems_price=enforced_sack_of_gems_price, minimum_allowed_sack_of_gems_price=minimum_allowed_sack_of_gems_price, retrieve_gem_price_from_scratch=retrieve_gem_price_from_scratch) return aggregated_badge_data
def get_listings(listing_output_file_name, retrieve_listings_from_scratch=False): if retrieve_listings_from_scratch: # Caveat: this update is only for items of Common rarity! update_all_listings_for_items_other_than_cards(rarity='common') all_listings = load_all_listings(listing_output_file_name) return all_listings
def aggregate_badge_data(badge_creation_details, badge_matches, all_listings=None, enforced_sack_of_gems_price=None, minimum_allowed_sack_of_gems_price=None, retrieve_gem_price_from_scratch=False): # Aggregate data: # owned appID --> (gem PRICE, sell price) # where: # - the gem price is the price required to buy gems on the market to then craft a booster pack for this game, # - the sell price is the price which sellers are asking for this booster pack. # # NB: ensure the same currency is used. if all_listings is None: all_listings = load_all_listings() gem_price = get_gem_price( enforced_sack_of_gems_price=enforced_sack_of_gems_price, minimum_allowed_sack_of_gems_price=minimum_allowed_sack_of_gems_price, retrieve_gem_price_from_scratch=retrieve_gem_price_from_scratch) badge_app_ids = list(badge_creation_details.keys()) aggregated_badge_data = dict() for app_id in badge_app_ids: app_name = badge_creation_details[app_id]['name'] gem_amount_required_to_craft_booster_pack = badge_creation_details[ app_id]['gem_value'] try: next_creation_time = badge_creation_details[app_id][ 'next_creation_time'] except KeyError: next_creation_time = None listing_hash = badge_matches[app_id] if listing_hash is None: # For some reason for Conran - The dinky Raccoon (appID = 612150), there is no listing of any "Booster Pack" # Reference: https://steamcommunity.com/market/search?appid=753&category_753_Game%5B0%5D=tag_app_612150 continue else: sell_price_in_cents = all_listings[listing_hash]['sell_price'] sell_price_in_euros = sell_price_in_cents / 100 aggregated_badge_data[app_id] = dict() aggregated_badge_data[app_id]['name'] = app_name aggregated_badge_data[app_id]['listing_hash'] = listing_hash aggregated_badge_data[app_id][ 'gem_amount'] = gem_amount_required_to_craft_booster_pack aggregated_badge_data[app_id][ 'gem_price'] = gem_amount_required_to_craft_booster_pack * gem_price aggregated_badge_data[app_id]['sell_price'] = sell_price_in_euros aggregated_badge_data[app_id][ 'next_creation_time'] = next_creation_time return aggregated_badge_data
def match_badges_with_listing_hashes(badge_creation_details=None, all_listings=None, verbose=True): # Badges for games which I own if badge_creation_details is None: badge_creation_details = parse_badge_creation_details() badge_app_ids = list(badge_creation_details.keys()) # Listings for ALL the existing Booster Packs if all_listings is None: all_listings = load_all_listings() all_listing_hashes = list(all_listings.keys()) # Dictionaries to match appIDs or app names with listing hashes listing_matches_with_app_ids = dict() listing_matches_with_app_names = dict() for listing_hash in all_listing_hashes: app_id = convert_listing_hash_to_app_id(listing_hash) app_name = convert_listing_hash_to_app_name(listing_hash) listing_matches_with_app_ids[app_id] = listing_hash listing_matches_with_app_names[app_name] = listing_hash # Match badges with listing hashes badge_matches = dict() for app_id in badge_app_ids: app_name = badge_creation_details[app_id]['name'] try: badge_matches[app_id] = listing_matches_with_app_ids[app_id] except KeyError: try: badge_matches[app_id] = listing_matches_with_app_names[ app_name] if verbose: print('Match for {} (appID = {}) with name instead of id.'. format(app_name, app_id)) except KeyError: badge_matches[app_id] = None if verbose: print('No match found for {} (appID = {})'.format( app_name, app_id)) if verbose: print('#badges = {} ; #matching hashes found = {}'.format( len(badge_app_ids), len(badge_matches))) return badge_matches
def load_apps_with_trading_cards(verbose=True): all_listings = load_all_listings() apps_with_trading_cards = [ convert_listing_hash_to_app_id(listing_hash) for listing_hash in all_listings ] if verbose: print('Apps with trading cards: {}'.format( len(apps_with_trading_cards))) return apps_with_trading_cards
def main(populate_all_item_name_ids=False): if populate_all_item_name_ids: # Pre-retrieval of ALL of the MISSING item name ids. # Caveat: this may require a long time, due to API rate limits. all_listings = load_all_listings() item_nameids = get_item_nameid_batch(listing_hashes=all_listings) else: aggregated_badge_data = load_aggregated_badge_data() populate_random_samples_of_badge_data(aggregated_badge_data, num_samples=50) return True
def get_listings_for_foil_cards(retrieve_listings_from_scratch, listing_output_file_name=None, verbose=True): if retrieve_listings_from_scratch: update_all_listings_for_foil_cards() if listing_output_file_name is None: listing_output_file_name = get_listing_output_file_name_for_foil_cards( ) all_listings = load_all_listings(listing_output_file_name) if verbose: print('#listings = {}'.format(len(all_listings))) return all_listings
def main(retrieve_listings_from_scratch=False, retrieve_market_orders_online=False, force_update_from_steam_card_exchange=False, enforced_sack_of_gems_price=None, minimum_allowed_sack_of_gems_price=None, use_a_constant_price_threshold=False, min_sell_price=30, min_num_listings=3, num_packs_to_display=10): # Load list of all listing hashes if retrieve_listings_from_scratch: update_all_listings() all_listings = load_all_listings() all_listings = filter_out_dubious_listing_hashes(all_listings) # Import information from SteamCardExchange aggregated_badge_data = fill_in_badge_data_with_data_from_steam_card_exchange( all_listings, force_update_from_steam_card_exchange= force_update_from_steam_card_exchange, enforced_sack_of_gems_price=enforced_sack_of_gems_price, minimum_allowed_sack_of_gems_price=minimum_allowed_sack_of_gems_price) # *Heuristic* filtering of listing hashes if use_a_constant_price_threshold: filtered_listing_hashes = filter_listings( all_listings, min_sell_price=min_sell_price, min_num_listings=min_num_listings) filtered_badge_data = fill_in_badge_data_with_data_from_steam_card_exchange( filtered_listing_hashes, force_update_from_steam_card_exchange= force_update_from_steam_card_exchange, enforced_sack_of_gems_price=enforced_sack_of_gems_price, minimum_allowed_sack_of_gems_price= minimum_allowed_sack_of_gems_price) else: filtered_badge_data = filter_out_badges_with_low_sell_price( aggregated_badge_data) filtered_listing_hashes = [ filtered_badge_data[app_id]['listing_hash'] for app_id in filtered_badge_data ] # Pre-retrieval of item name ids item_nameids = get_item_nameid_batch(filtered_listing_hashes) # Download market orders market_order_dict = load_market_order_data( filtered_badge_data, trim_output=True, retrieve_market_orders_online=retrieve_market_orders_online) # Only keep marketable booster packs marketable_market_order_dict, unknown_market_order_dict = filter_out_unmarketable_packs( market_order_dict) # Sort by bid value hashes_for_best_bid = sort_according_to_buzz(market_order_dict, marketable_market_order_dict) # Display the highest ranked booster packs print_packs_with_high_buzz(hashes_for_best_bid, market_order_dict, num_packs_to_display=num_packs_to_display) # Detect potential arbitrages badge_arbitrages = find_badge_arbitrages(filtered_badge_data, market_order_dict) print('\n# Results for detected *potential* arbitrages\n') print_arbitrages(badge_arbitrages, use_numbered_bullet_points=True, use_hyperlink=True) return
def determine_whether_an_arbitrage_might_exist_for_foil_cards( eligible_listing_hashes, all_goo_details, app_ids_with_unreliable_goo_details=None, app_ids_with_unknown_goo_value=None, all_listings=None, listing_output_file_name=None, sack_of_gems_price_in_euros=None, retrieve_gem_price_from_scratch=True, verbose=True): if sack_of_gems_price_in_euros is None: # Load the price of a sack of 1000 gems sack_of_gems_price_in_euros = load_sack_of_gems_price( retrieve_gem_price_from_scratch=retrieve_gem_price_from_scratch, verbose=verbose) if listing_output_file_name is None: listing_output_file_name = get_listing_output_file_name_for_foil_cards( ) if all_listings is None: all_listings = load_all_listings( listing_output_file_name=listing_output_file_name) if app_ids_with_unreliable_goo_details is None: app_ids_with_unreliable_goo_details = [] if app_ids_with_unknown_goo_value is None: app_ids_with_unknown_goo_value = [] num_gems_per_sack_of_gems = get_num_gems_per_sack_of_gems() sack_of_gems_price_in_cents = 100 * sack_of_gems_price_in_euros arbitrages = dict() for listing_hash in eligible_listing_hashes: app_id = convert_listing_hash_to_app_id(listing_hash) if app_id in app_ids_with_unreliable_goo_details: # NB: This is for goo details which were retrieved with the default item type n° (=2), which can be wrong. if verbose: print( '[X]\tUnreliable goo details for {}'.format(listing_hash)) continue goo_value_in_gems = safe_read_from_dict(input_dict=all_goo_details, input_key=app_id) if app_id in app_ids_with_unknown_goo_value or goo_value_in_gems is None: # NB: This is when the goo value is unknown, despite a correct item type n° used to download goo details. if verbose: print('[?]\tUnknown goo value for {}'.format(listing_hash)) continue goo_value_in_cents = goo_value_in_gems / num_gems_per_sack_of_gems * sack_of_gems_price_in_cents current_listing = all_listings[listing_hash] ask_in_cents = current_listing['sell_price'] if ask_in_cents == 0: # NB: The ask cannot be equal to zero. So, we skip the listing because of there must be a bug. if verbose: print('[!]\tImpossible ask price ({:.2f}€) for {}'.format( ask_in_cents / 100, listing_hash, )) continue profit_in_cents = goo_value_in_cents - ask_in_cents is_arbitrage = bool(profit_in_cents > 0) if is_arbitrage: arbitrage = dict() arbitrage['profit'] = profit_in_cents / 100 arbitrage['ask'] = ask_in_cents / 100 arbitrage['goo_amount'] = goo_value_in_gems arbitrage['goo_value'] = goo_value_in_cents / 100 arbitrages[listing_hash] = arbitrage return arbitrages