Esempio n. 1
0
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']
Esempio n. 3
0
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)