class MerchantSampleLogic(MerchantBaseLogic): def __init__(self): MerchantBaseLogic.__init__(self) global settings self.settings = settings ''' Information store ''' self.products = {} self.offers = {} ''' Predefined API token ''' self.merchant_id = settings['merchant_id'] self.merchant_token = merchant_token ''' Setup API ''' PricewarsRequester.add_api_token(self.merchant_token) self.marketplace_api = MarketplaceApi( host=self.settings['marketplace_url']) self.producer_api = ProducerApi(host=self.settings['producer_url']) ''' Start Logic Loop ''' self.run_logic_loop() def update_api_endpoints(self): """ Updated settings may contain new endpoints, so they need to be set in the api client as well. However, changing the endpoint (after simulation start) may lead to an inconsistent state :return: None """ self.marketplace_api.host = self.settings['marketplace_url'] self.producer_api.host = self.settings['producer_url'] ''' Implement Abstract methods / Interface ''' def get_settings(self): return self.settings def update_settings(self, new_settings): def cast_to_expected_type(key, value, def_settings=self.settings): if key in def_settings: return type(def_settings[key])(value) else: return value new_settings_casted = dict([ (key, cast_to_expected_type(key, new_settings[key])) for key in new_settings ]) self.settings.update(new_settings_casted) self.update_api_endpoints() return self.settings def sold_offer(self, offer): #TODO: we store the amount in self.offers but do not decrease it here if self.state != 'running': return try: offers = self.marketplace_api.get_offers() self.buy_product_and_update_offer(offers) except Exception as e: print('error on handling a sold offer:', e) ''' Merchant Logic for being the cheapest ''' def setup(self): try: marketplace_offers = self.marketplace_api.get_offers() for i in range(settings['initialProducts']): self.buy_product_and_update_offer(marketplace_offers) except Exception as e: print('error on setup:', e) def execute_logic(self): try: offers = self.marketplace_api.get_offers() items_offered = sum( o.amount for o in offers if o.merchant_id == self.settings['merchant_id']) while items_offered < (settings['initialProducts'] - 1): self.buy_product_and_update_offer(offers) items_offered = sum( o.amount for o in self.marketplace_api.get_offers() if o.merchant_id == self.settings['merchant_id']) for product in self.products.values(): if product.uid in self.offers: offer = self.offers[product.uid] offer.price = self.calculate_prices( offers, product.uid, product.price, product.product_id) try: self.marketplace_api.update_offer(offer) except Exception as e: print('error on updating an offer:', e) else: print('ERROR: product UID is not in offers; skipping.') except Exception as e: print('error on executing the logic:', e) return settings['maxReqPerSec'] / 10 def calculate_prices(self, marketplace_offers, product_uid, purchase_price, product_id): competitive_offers = [] [ competitive_offers.append(offer) for offer in marketplace_offers if offer.merchant_id != self.merchant_id and offer.product_id == product_id ] cheapest_offer = 999 if len(competitive_offers) == 0: return 2 * purchase_price for offer in competitive_offers: if offer.price < cheapest_offer: cheapest_offer = offer.price new_price = cheapest_offer - settings['price_decrement'] if new_price < purchase_price: new_price = purchase_price return new_price def add_new_product_to_offers(self, new_product, marketplace_offers): new_offer = Offer.from_product(new_product) new_offer.price = self.calculate_prices(marketplace_offers, new_product.uid, new_product.price, new_product.product_id) new_offer.shipping_time = { 'standard': settings['shipping'], 'prime': settings['primeShipping'] } new_offer.prime = True try: new_offer.offer_id = self.marketplace_api.add_offer( new_offer).offer_id self.products[new_product.uid] = new_product self.offers[new_product.uid] = new_offer except Exception as e: print('error on adding a new offer:', e) def restock_existing_product(self, new_product, marketplace_offers): # print('restock product', new_product) product = self.products[new_product.uid] product.amount += new_product.amount product.signature = new_product.signature offer = self.offers[product.uid] # print('in this offer:', offer) offer.price = self.calculate_prices(marketplace_offers, product.uid, product.price, product.product_id) offer.amount = product.amount offer.signature = product.signature try: self.marketplace_api.restock(offer.offer_id, new_product.amount, offer.signature) except Exception as e: print('error on restocking an offer:', e) def buy_product_and_update_offer(self, marketplace_offers): try: new_product = self.producer_api.buy_product() if new_product.uid in self.products: self.restock_existing_product(new_product, marketplace_offers) else: self.add_new_product_to_offers(new_product, marketplace_offers) except Exception as e: print('error on buying a new product:', e)
class BaysianMerchant(MerchantBaseLogic): def __init__(self): MerchantBaseLogic.__init__(self) global settings self.settings = settings ''' Predefined API token ''' self.merchant_id = settings['merchant_id'] self.merchant_token = merchant_token ''' Setup API ''' PricewarsRequester.add_api_token(self.merchant_token) self.marketplace_api = MarketplaceApi(host=self.settings['marketplace_url']) self.producer_api = ProducerApi(host=self.settings['producer_url']) ''' Setup ML model ''' self.models_per_product = self.load_models_from_filesystem() self.last_learning = datetime.datetime.now() trigger_learning(self.merchant_token, settings['kafka_reverse_proxy_url']) ''' Start Logic Loop ''' self.run_logic_loop() @staticmethod def load_models_from_filesystem(folder='models'): result = {} for root, dirs, files in os.walk(make_relative_path(folder)): pkl_files = [f for f in files if f.endswith('.pkl')] for pkl_file in pkl_files: complete_path = os.path.join(root, pkl_file) try: product_id = int(pkl_file.split('.')[0]) result[product_id] = joblib.load(complete_path) # print(result[product_id].coef_) except ValueError: # do not load model files, that don't have the naming scheme pass break return result def update_api_endpoints(self): """ Updated settings may contain new endpoints, so they need to be set in the api client as well. However, changing the endpoint (after simulation start) may lead to an inconsistent state :return: None """ self.marketplace_api.host = self.settings['marketplace_url'] self.producer_api.host = self.settings['producer_url'] ''' Implement Abstract methods / Interface ''' def update_settings(self, new_settings): MerchantBaseLogic.update_settings(self, new_settings) self.update_api_endpoints() return self.settings def sold_offer(self, offer): pass ''' Merchant Logic ''' def price_product(self, product_or_offer, product_prices_by_uid, current_offers=None): """ Computes a price for a product based on trained models or (exponential) random fallback :param product_or_offer: product object that is to be priced :param current_offers: list of offers :return: """ price = product_prices_by_uid[product_or_offer.uid] try: model = self.models_per_product[product_or_offer.product_id] offer_df = pd.DataFrame([o.to_dict() for o in current_offers]) offer_df = offer_df[offer_df['product_id'] == product_or_offer.product_id] own_offers_mask = offer_df['merchant_id'] == self.merchant_id features = [] for potential_price in range(1, 100, 1): potential_price_candidate = potential_price / 10.0 potential_price = price + potential_price_candidate #product_or_offer.price + potential_price_candidate offer_df.loc[own_offers_mask, 'price'] = potential_price features.append(extract_features_from_offer_snapshot(offer_df, self.merchant_id, product_id=product_or_offer.product_id)) data = pd.DataFrame(features).dropna() try: filtered = data[['amount_of_all_competitors', 'average_price_on_market', 'distance_to_cheapest_competitor', 'price_rank', 'quality_rank' ]] data['sell_prob'] = model.predict(filtered)#model.predict_proba(filtered)[:,1] data['expected_profit'] = data['sell_prob'] * (data['own_price'] - price) print("set price as ", data['own_price'][data['expected_profit'].argmax()]) except Exception as e: print("Setting price failed: ", e) return data['own_price'][data['expected_profit'].argmax()] except (KeyError, ValueError) as e: return price * (np.random.exponential() + 0.99) except Exception as e: pass def execute_logic(self): next_training_session = self.last_learning \ + datetime.timedelta(minutes=self.settings['minutes_between_learnings']) if next_training_session <= datetime.datetime.now(): self.last_learning = datetime.datetime.now() trigger_learning(self.merchant_token, self.settings['kafka_reverse_proxy_url']) request_count = 0 self.models_per_product = self.load_models_from_filesystem() try: offers = self.marketplace_api.get_offers(include_empty_offers=True) except Exception as e: print('error on getting offers:', e) return max(1.0, request_count) / settings['max_req_per_sec'] own_offers = [offer for offer in offers if offer.merchant_id == self.merchant_id] own_offers_by_uid = {offer.uid: offer for offer in own_offers} missing_offers = settings['max_amount_of_offers'] - sum(offer.amount for offer in own_offers) new_products = [] for _ in range(missing_offers): try: prod = self.producer_api.buy_product() new_products.append(prod) except: pass products = self.producer_api.get_products() product_prices_by_uid = {product.uid: product.price for product in products} for own_offer in own_offers: if own_offer.amount > 0: own_offer.price = self.price_product(own_offer, product_prices_by_uid, current_offers=offers) try: self.marketplace_api.update_offer(own_offer) request_count += 1 except Exception as e: print('error on updating offer:', e) for product in new_products: try: if product.uid in own_offers_by_uid: offer = own_offers_by_uid[product.uid] offer.amount += product.amount offer.signature = product.signature try: self.marketplace_api.restock(offer.offer_id, amount=product.amount, signature=product.signature) except Exception as e: print('error on restocking an offer:', e) offer.price = self.price_product(product, product_prices_by_uid, current_offers=offers) try: self.marketplace_api.update_offer(offer) request_count += 1 except Exception as e: print('error on updating an offer:', e) else: offer = Offer.from_product(product) offer.prime = True offer.shipping_time['standard'] = self.settings['shipping'] offer.shipping_time['prime'] = self.settings['primeShipping'] offer.merchant_id = self.merchant_id offer.price = self.price_product(product, product_prices_by_uid, current_offers=offers+[offer]) try: self.marketplace_api.add_offer(offer) except Exception as e: print('error on adding an offer to the marketplace:', e) except Exception as e: print('could not handle product:', product, e) return max(1.0, request_count) / settings['max_req_per_sec']
class SecondCheapestMerchantApp(MerchantBaseLogic): def __init__(self): MerchantBaseLogic.__init__(self) global settings self.settings = settings ''' Predefined API token ''' self.merchant_id = settings['merchant_id'] self.merchant_token = merchant_token ''' Setup API ''' PricewarsRequester.add_api_token(merchant_token) self.marketplace_api = MarketplaceApi( host=self.settings['marketplace_url']) self.producer_api = ProducerApi(host=self.settings['producer_url']) ''' Start Logic Loop ''' self.run_logic_loop() ''' save purchase prices for offer updates ''' self.purchase_prices = {} def update_api_endpoints(self): self.marketplace_api.host = self.settings['marketplace_url'] self.producer_api.host = self.settings['producer_url'] def update_settings(self, new_settings): MerchantBaseLogic.update_settings(self, new_settings) self.update_api_endpoints() return self.settings def initialize_purchase_price_map(self): try: available_products = self.producer_api.get_products() except Exception as e: print('error on getting products from the producer:', e) for product in available_products: self.purchase_prices[product.uid] = product.price def buy_product_and_post_to_marketplace(self, all_offers): print('buy Product and update') new_product = self.buy_product() existing_offers = self.get_existing_offers_for_product_id_from_marketplace( all_offers, new_product.product_id) target_price = self.get_second_cheapest_price(existing_offers, new_product.price) existing_offer = self.get_own_offer_for_product_uid( existing_offers, new_product.uid) return self.post_offer(new_product, target_price, existing_offer) def buy_product(self): try: new_product = self.producer_api.buy_product() if new_product.uid not in self.purchase_prices: self.purchase_prices[new_product.uid] = new_product.price return new_product except Exception as e: print('error on buying a new product:', e) def get_existing_offers_for_product_id_from_marketplace( self, all_offers, product_id): product_id_offers = [ offer for offer in all_offers if offer.product_id == product_id ] return product_id_offers def get_second_cheapest_price(self, offers, purchase_price): maximum_price = 2 * purchase_price minimum_price = purchase_price * ( 1 + (self.settings['minimumMarginPercentile'] / 100.0)) second_cheapest_offer = cheapest_offer = maximum_price for offer in offers: if offer.merchant_id == self.merchant_id: continue if offer.price < cheapest_offer: second_cheapest_offer = cheapest_offer cheapest_offer = offer.price elif cheapest_offer < offer.price < second_cheapest_offer: second_cheapest_offer = offer.price target_price = second_cheapest_offer - self.settings['priceDifference'] if second_cheapest_offer <= maximum_price and target_price >= cheapest_offer: second_cheapest_offer = target_price if second_cheapest_offer < minimum_price: second_cheapest_offer = minimum_price return second_cheapest_offer def get_own_offer_for_product_uid(self, offers, product_uid): return next( (offer for offer in offers if offer.merchant_id == self.merchant_id and offer.uid == product_uid), None) def post_offer(self, product, price, existing_offer): new_offer = Offer.from_product(product) new_offer.price = price new_offer.shipping_time = { 'standard': settings['shipping'], 'prime': settings['primeShipping'] } new_offer.prime = True try: if existing_offer is None: return self.marketplace_api.add_offer(new_offer) else: self.marketplace_api.restock(existing_offer.offer_id, product.amount, product.signature) return None except Exception as e: print('error on posting an offer:', e) def update_offer(self, own_offer, target_price): if own_offer.price != target_price: own_offer.price = target_price try: self.marketplace_api.update_offer(own_offer) except Exception as e: print('error on updating an offer:', e) def get_own_offers(self, all_offers): return [ offer for offer in all_offers if offer.merchant_id == self.merchant_id ] def get_amount_of_own_offers(self, all_offers): return sum(offer.amount for offer in self.get_own_offers(all_offers)) def adjust_prices(self, all_offers): print('Update offer') try: my_offered_product_ids = [ offer.product_id for offer in all_offers if offer.merchant_id == self.merchant_id ] all_offers_i_offer_as_well = [ offer for offer in all_offers if offer.product_id in my_offered_product_ids ] # Create a map with the product_id as key and the amount of offers as value (includes my own offers) offers_per_traded_product = {} for offer in all_offers_i_offer_as_well: offers_per_traded_product[ offer.product_id] = offers_per_traded_product.get( offer.product_id, 0) + 1 # Iterate over the traded product IDs in descending order of the amount of competitor offers for product_id in [ offer_product_id[0] for offer_product_id in sorted( offers_per_traded_product.items(), key=operator.itemgetter(1), reverse=True) ]: existing_offers_for_product_id = self.get_existing_offers_for_product_id_from_marketplace( all_offers_i_offer_as_well, product_id) # Iterate over my offers based on the quality, starting with the best quality (lowest quality number) for product_uid in [ offer.uid for offer in sorted( self.get_own_offers( existing_offers_for_product_id), key=lambda offer_entry: offer_entry.quality) ]: purchase_price = self.purchase_prices[product_uid] target_price = self.get_second_cheapest_price( existing_offers_for_product_id, purchase_price) existing_offer = self.get_own_offer_for_product_uid( existing_offers_for_product_id, product_uid) self.update_offer(existing_offer, target_price) except Exception as e: print('error on adjusting prices:', e) def refill_offers(self, all_offers=None): if not all_offers: try: all_offers = self.marketplace_api.get_offers( include_empty_offers=True) except Exception as e: print('error on fetching offers from the marketplace:', e) existing_offers = self.get_amount_of_own_offers(all_offers) try: for i in range(settings['listedOffers'] - existing_offers): new_product = self.buy_product_and_post_to_marketplace( all_offers) if new_product: all_offers.append(new_product) # else: product already offered and restocked except Exception as e: print('error on refilling offers:', e) def setup(self): try: self.initialize_purchase_price_map() all_offers = self.marketplace_api.get_offers( include_empty_offers=True) self.refill_offers(all_offers) except Exception as e: print('error on setting up offers:', e) def execute_logic(self): try: all_offers = self.marketplace_api.get_offers( include_empty_offers=True) self.adjust_prices(all_offers) self.refill_offers(all_offers) except Exception as e: print('error on executing logic:', e) # ToDo: Return true value (calculate!) return self.interval def sold_offer(self, offer_json): if self.state != 'running': return try: all_offers = self.marketplace_api.get_offers( include_empty_offers=True) self.refill_offers(all_offers) except Exception as e: print('error on handling sold offers:', e)