Example #1
0
 def __init__(self):
     MerchantBaseLogic.__init__(self)
     global settings
     self.settings = settings
     '''
         Internal state handling
     '''
     self.execQueue = []
     '''
         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()
Example #2
0
 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, self.merchant_id,
                      settings['kafka_reverse_proxy_url'])
     '''
         Start Logic Loop
     '''
     self.run_logic_loop()
Example #3
0
 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'])
     '''
         Start Logic Loop
     '''
     self.run_logic_loop()
Example #4
0
class MerchantSampleLogic(MerchantBaseLogic):
    def __init__(self):
        MerchantBaseLogic.__init__(self)
        global settings
        self.settings = settings
        '''
            Internal state handling
        '''
        self.execQueue = []
        '''
            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']

    def update_settings(self, new_settings):
        MerchantBaseLogic.update_settings(self, new_settings)
        self.update_api_endpoints()
        return self.settings

    '''
        Implement Abstract methods / Interface
    '''

    def sold_offer(self, offer):
        self.execQueue.append((self.sold_product, [offer]))

    '''
        Merchant Logic
    '''

    def setup(self):
        try:
            for i in range(settings['initialProducts']):
                self.buy_product_and_update_offer()
        except Exception as e:
            print('error on setup:', e)

    def execute_logic(self):
        # execute queued methods
        tmp_queue = [e for e in self.execQueue]
        self.execQueue = []
        for method, args in tmp_queue:
            method(*args)

        try:
            offers = self.marketplace_api.get_offers()
        except Exception as e:
            print('error on fetching current offers:', e)

        missing_offers = self.settings["initialProducts"] - len(self.offers)
        for missing_offer in range(missing_offers):
            self.buy_product_and_update_offer()

        for product in self.products.values():
            competitor_offers = []
            for offer in offers:
                if offer.merchant_id != self.merchant_id and offer.id == product.id:
                    competitor_offers.append(offer.price)
            if len(competitor_offers) > 0:
                offer = self.offers[product.uid]
                self.adjust_prices(
                    offer=offer,
                    product=product,
                    lowest_competitor_price=min(competitor_offers))

        return settings['max_req_per_sec'] / 60

    def adjust_prices(self,
                      offer=None,
                      product=None,
                      lowest_competitor_price=0):
        if not offer or not product:
            return
        min_price = product.price + settings['minPriceMargin']
        max_price = product.price + settings['maxPriceMargin']
        price = lowest_competitor_price - settings['priceDecrease']
        price = min(price, max_price)
        if price < min_price:
            price = max_price
        offer.price = price
        try:
            self.marketplace_api.update_offer(offer)
        except Exception as e:
            print('error on updating an offer:', e)

    def sold_product(self, sold_offer):
        if sold_offer.uid in self.offers:
            offer = self.offers[sold_offer.uid]
            offer.amount -= sold_offer.amount_sold
            product = self.products[sold_offer.uid]
            product.amount -= sold_offer.amount_sold
            self.buy_product_and_update_offer()

    def add_new_product_to_offers(self, new_product):
        new_offer = Offer.from_product(new_product)
        new_offer.price += settings['maxPriceMargin']
        new_offer.shipping_time = {
            'standard': settings['shipping'],
            'prime': settings['primeShipping']
        }
        new_offer.prime = True
        self.products[new_product.uid] = new_product
        try:
            new_offer.offer_id = self.marketplace_api.add_offer(
                new_offer).offer_id
            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):
        product = self.products[new_product.uid]
        product.amount += new_product.amount
        product.signature = new_product.signature

        offer = self.offers[product.uid]
        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):
        new_product = self.producer_api.buy_product()

        if new_product.uid in self.products:
            self.restock_existing_product(new_product)
        else:
            self.add_new_product_to_offers(new_product)
Example #5
0
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, self.merchant_id,
                         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.merchant_id,
                             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']
Example #6
0
class MerchantD(MerchantBaseLogic):
    def __init__(self):
        MerchantBaseLogic.__init__(self)
        global settings
        self.settings = settings
        '''
            Internal state handling
        '''
        self.execQueue = []
        self.marketplace_requests = []
        '''
            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'], debug=False)
        self.producer_api = ProducerApi(host=self.settings['producer_url'],
                                        debug=False)
        '''
            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']

    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):
        print("sold offer")
        self.execQueue.append((self.sold_product, [offer]))

    def buy_missing_products(self):
        for i in range(self.settings["initialProducts"] -
                       sum(offer.amount for offer in self.offers.values())):
            self.buy_product_and_update_offer()

    def setup(self):
        try:
            # get all products for later comparison over all qualities
            self.product = {}
            for product in self.producer_api.get_products():
                self.products[product.uid] = product

            # get all existing offers from marketplace
            self.offer = {}
            for offer in self.marketplace_api.get_offers():
                if offer.merchant_id == self.merchant_id:
                    self.offers[offer.uid] = offer

            # buy new products if none or not enough exist
            self.buy_missing_products()

        except Exception as e:
            print('error on setup:', e)

    def execute_logic(self):
        # execute queued methods
        tmp_queue = [e for e in self.execQueue]
        self.execQueue = []
        for method, args in tmp_queue:
            method(*args)

        # if initialProducts setting increased after start, get new products
        self.buy_missing_products()

        self.update_market_situation()

        return self.calculate_intervall()

    def base_price_diff(self, offer):
        product = self.products[offer.uid]
        return offer.price - product.price

    def adjust_prices(self, offer=None, lowest_price_diff=0):
        product = self.products[offer.uid]
        if not offer or not product:
            return
        price_diff = min(lowest_price_diff - settings['priceDecrease'],
                         settings['maxPriceMargin'])
        if price_diff < settings['minPriceMargin']:
            price_diff = settings['maxPriceMargin']
        new_product_price = product.price + price_diff
        if new_product_price != offer.price:
            offer.price = new_product_price
            print("update to new price ", new_product_price)
            self.marketplace_api.update_offer(offer)
            self.request_done()

    def update_market_situation(self):
        marketplace_offers = self.marketplace_api.get_offers()
        for own_offer in self.offers.values():
            if self.quora_exhausted():
                break
            if own_offer.amount > 0:
                competitor_offers_price_diff = []
                for marketplace_offer in marketplace_offers:
                    if marketplace_offer.merchant_id != self.merchant_id and marketplace_offer.product_id == own_offer.product_id:
                        competitor_offers_price_diff.append(
                            self.base_price_diff(marketplace_offer))
                if len(competitor_offers_price_diff) > 0:
                    self.adjust_prices(
                        offer=own_offer,
                        lowest_price_diff=min(competitor_offers_price_diff))
                else:
                    self.adjust_prices(offer=own_offer)

    def sold_product(self, sold_offer):
        print('soldProduct, offer:', sold_offer)
        if sold_offer.uid in self.offers:
            # print('found in offers')
            offer = self.offers[sold_offer.uid]
            offer.amount -= sold_offer.amount_sold
            product = self.products[sold_offer.uid]
            product.amount -= sold_offer.amount_sold
            if product.amount <= 0:
                print('product {:d} is out of stock!'.format(product.uid))
            self.buy_product_and_update_offer()

    def add_new_product_to_offers(self, new_product):
        new_offer = Offer.from_product(new_product)
        new_offer.price += settings['maxPriceMargin']
        new_offer.shipping_time = {
            'standard': settings['shipping'],
            'prime': settings['primeShipping']
        }
        new_offer.prime = True
        # self.products[new_product.uid] = new_product
        new_offer.offer_id = self.marketplace_api.add_offer(new_offer).offer_id
        self.offers[new_product.uid] = new_offer

    def restock_existing_product(self, new_product):
        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.amount = product.amount
        offer.signature = product.signature
        self.marketplace_api.restock(offer.offer_id, new_product.amount,
                                     offer.signature)

    def buy_product_and_update_offer(self):
        print('buy Product and update')
        new_product = self.producer_api.buy_product()

        if new_product.uid in self.offers:
            self.restock_existing_product(new_product)
        else:
            self.add_new_product_to_offers(new_product)

    def request_done(self):
        self.marketplace_requests.insert(0, time.time())

    def quora_exhausted(self):
        while len(self.marketplace_requests) > 0:
            last = self.marketplace_requests.pop()
            now = time.time()
            if now - last < 1:
                self.marketplace_requests.append(last)
                break

        return len(self.marketplace_requests) >= settings['max_req_per_sec']

    def active_offers_count(self):
        offer_count = 0
        for offer in self.offers.values():
            if offer.amount > 0:
                offer_count += 1
        return offer_count

    def calculate_intervall(self):
        if len(self.marketplace_requests) == 0:
            return 0
        else:
            offer_count = self.active_offers_count()
            remaining_reqs = settings['max_req_per_sec'] - len(
                self.marketplace_requests)
            time_to_next_release = time.time() - self.marketplace_requests[
                len(self.marketplace_requests) - 1]
            return (offer_count / (remaining_reqs + 1)) * time_to_next_release
Example #7
0
class MerchantSampleLogic(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'])

        '''
            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 update_settings(self, new_settings):
        MerchantBaseLogic.update_settings(self, new_settings)
        self.update_api_endpoints()
        return self.settings

    def sold_offer(self, offer):
        print('sold offer:', offer)

    '''
        Merchant Logic
    '''

    def price_product(self, product):
        return (1.0 + self.settings['fixed_margin_perc'] / 100.0) * product.price

    def execute_logic(self):
        try:
            offers = self.marketplace_api.get_offers(include_empty_offers=True)
        except Exception as e:
            print('error on getting offers from the marketplace:', e)
            return 1.0 / 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

        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
                    self.marketplace_api.restock(offer.offer_id, amount=product.amount, signature=product.signature)
                    offer.price = self.price_product(product)
                    self.marketplace_api.update_offer(offer)
                else:
                    offer = Offer.from_product(product)
                    offer.price = self.price_product(product)
                    offer.prime = True
                    offer.shipping_time['standard'] = self.settings['shipping']
                    offer.shipping_time['prime'] = self.settings['primeShipping']
                    self.marketplace_api.add_offer(offer)
            except Exception as e:
                print('could not handle product:', product, e)

        return 1.0 / settings['max_req_per_sec']
Example #8
0
 def __init__(self, merchant_token, marketplace_url, producer_url):
     PricewarsRequester.add_api_token(merchant_token)
     self.marketplace_api = MarketplaceApi(host=marketplace_url)
     self.producer_api = ProducerApi(host=producer_url)
     self.request_counter = 0
Example #9
0
class Api(ApiAbstraction):
    def __init__(self, merchant_token, marketplace_url, producer_url):
        PricewarsRequester.add_api_token(merchant_token)
        self.marketplace_api = MarketplaceApi(host=marketplace_url)
        self.producer_api = ProducerApi(host=producer_url)
        self.request_counter = 0

    def add_offer(self, offer: Offer) -> Offer:
        try:
            return self.marketplace_api.add_offer(offer)
        except Exception as e:
            print('error on adding an offer to the marketplace:', e)

    def unregister_merchant(self, merchant_token=''):
        return self.marketplace_api.unregister_merchant(merchant_token)

    def register_merchant(self,
                          api_endpoint_url='',
                          merchant_name='',
                          algorithm_name='') -> MerchantRegisterResponse:
        return self.marketplace_api.register_merchant(api_endpoint_url,
                                                      merchant_name,
                                                      algorithm_name)

    def update_offer(self, offer: Offer):
        try:
            self.increase_request_counter()
            return self.marketplace_api.update_offer(offer)
        except Exception as e:
            logging.warning(
                'Could not update offer on marketplace: {}'.format(e))

    def get_offers(self, include_empty_offers=False) -> List[Offer]:
        try:
            return self.marketplace_api.get_offers(include_empty_offers)
        except Exception as e:
            logging.warning(
                'Could not receive offers from marketplace: {}'.format(e))
            raise e

    def restock(self, offer_id=-1, amount=0, signature=''):
        try:
            return self.marketplace_api.restock(offer_id, amount, signature)
        except Exception as e:
            print('error on restocking an offer:', e)

    def add_product(self, product: Product):
        return self.producer_api.add_product(product)

    def get_product(self, product_uid) -> Product:
        return self.producer_api.get_product(product_uid)

    def add_products(self, products: List[Product]):
        return self.producer_api.add_products(products)

    def update_product(self, product: Product):
        return self.producer_api.update_product(product)

    def update_products(self, products: List[Product]):
        return self.producer_api.update_products(products)

    def delete_product(self, product_uid):
        return self.producer_api.delete_product(product_uid)

    def get_products(self) -> List[Product]:
        try:
            return self.producer_api.get_products()
        except Exception as e:
            logging.warning(
                'Could not receive products from producer api: {}'.format(e))
        return list()

    def buy_product(self) -> Product:
        try:
            return self.producer_api.buy_product()
        except Exception as e:
            logging.warning(
                'Could not buy new product from producer api: {}'.format(e))
            raise e

    def update_marketplace_url(self, marketplace_url: str):
        self.marketplace_api.host = marketplace_url

    def update_producer_url(self, producer_url: str):
        self.producer_api.host = producer_url

    def increase_request_counter(self):
        self.request_counter += 1

    def reset_request_counter(self):
        self.request_counter = 0
class MerchantSampleLogic(MerchantBaseLogic):
    def __init__(self):
        MerchantBaseLogic.__init__(self)
        global settings
        self.settings = settings
        '''
            Internal state handling
        '''
        self.execQueue = []
        '''
            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):
        self.execQueue.append((self.sold_product, [offer]))

    '''
        Merchant Logic
    '''

    def setup(self):
        try:
            for i in range(settings['initialProducts']):
                self.buy_product_and_update_offer()
        except Exception as e:
            print('error on setup:', e)

    def execute_logic(self):
        # execute queued methods
        tmp_queue = [e for e in self.execQueue]
        self.execQueue = []
        for method, args in tmp_queue:
            method(*args)

        try:
            offers = self.marketplace_api.get_offers()

            for product in self.products.values():
                if product.amount > 0:
                    competitor_offers = []
                    for offer in offers:
                        if offer.merchant_id != self.merchant_id and offer.product_id == product.product_id:
                            competitor_offers.append(offer.price)
                    offer = self.offers[product.uid]
                    if len(competitor_offers) > 0:
                        competitor_offers.sort()
                        if len(competitor_offers) > 2:
                            self.adjust_prices(
                                offer=offer,
                                product=product,
                                lowest_competitor_price=competitor_offers[0],
                                second_competitor_price=competitor_offers[1],
                                third_competitor_price=competitor_offers[2])
                        elif len(competitor_offers) > 1:
                            self.adjust_prices(
                                offer=offer,
                                product=product,
                                lowest_competitor_price=competitor_offers[0],
                                second_competitor_price=competitor_offers[1],
                                third_competitor_price=0)
                        else:
                            self.adjust_prices(
                                offer=offer,
                                product=product,
                                lowest_competitor_price=competitor_offers[0],
                                second_competitor_price=0,
                                third_competitor_price=0)

        except Exception as e:
            print('error on executing logic:', e)
        # returns sleep value;
        return 60.0 / settings['max_req_per_sec']

    def adjust_prices(self,
                      offer=None,
                      product=None,
                      lowest_competitor_price=0,
                      second_competitor_price=0,
                      third_competitor_price=0):
        if not offer or not product:
            return
        min_price = product.price
        max_price = product.price * 2
        target_position = randint(1, 3)
        if (target_position == 3 and third_competitor_price > 0):
            price = third_competitor_price - settings['price_decrement']
        elif (target_position == 2 and second_competitor_price > 0):
            price = second_competitor_price - settings['price_decrement']
        else:
            price = lowest_competitor_price - settings['price_decrement']
        price = min(price, max_price)
        if price < min_price:
            price = max_price
        if price != offer.price:
            offer.price = price
            try:
                self.marketplace_api.update_offer(offer)
            except Exception as e:
                print('error on updating offer:', e)

    def sold_product(self, sold_offer):
        print('soldProduct, offer:', sold_offer)
        if sold_offer.uid in self.offers:
            print('found in offers')
            offer = self.offers[sold_offer.uid]
            offer.amount -= sold_offer.amount_sold
            product = self.products[sold_offer.uid]
            product.amount -= sold_offer.amount_sold
            if product.amount <= 0:
                print('product {:d} is out of stock!'.format(product.uid))
            self.buy_product_and_update_offer()

    def add_new_product_to_offers(self, new_product):
        new_offer = Offer.from_product(new_product)
        new_offer.price = new_offer.price * 2
        new_offer.shipping_time = {
            'standard': settings['shipping'],
            'prime': settings['primeShipping']
        }
        new_offer.prime = True
        self.products[new_product.uid] = new_product
        new_offer.offer_id = self.marketplace_api.add_offer(new_offer).offer_id
        self.offers[new_product.uid] = new_offer

    def restock_existing_product(self, new_product):
        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.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 offer:', e)

    def buy_product_and_update_offer(self):
        print('buy Product and update')
        try:
            new_product = self.producer_api.buy_product()

            if new_product.uid in self.products:
                self.restock_existing_product(new_product)
            else:
                self.add_new_product_to_offers(new_product)
        except Exception as e:
            print('error on buying a new product:', e)
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['price_decrement']
        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)
Example #12
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)