Skip to content

How to write a bidding agent

Rémi Attab edited this page Mar 21, 2013 · 25 revisions

Introduction

In this document we'll write a simple fixed price bidding agent. This will be made up of three major components:

  • The agent's configuration which controls the type of bid requests that the router will forward to the agent.
  • The bidding logic which decides how much we want to bid on each spots.
  • The pacer which controls the rate at which we spend our budget. The annotated source code for the full bidding agent that we will present in this example is available rtbkit's repository.

Agent Configuration

In this section, we'll create an AgentConfig object which will hold basic information about our bidding agent as well as setup various filters which will ensure that the routers will only forward bid requests that we're interesting in.

Let's get started:

AgentConfig config;

void setConfig()
{
	config = AgentConfig();

Pretty straightforward begining but we had to start somewhere.

    config.account = {"a", "very", "long", "account", "name", "thingy"};

Here we set up the account using the new and fancy C++11 initialization list[^1]. Accounts are used to keep track of the budget associated with each campaign and they're organized in a hierarchy. How this hierarchy is setup is entirely up to the writer of the bidding agents. A typical setup would be <campaign>.<strategy> but, as you can see in the example, you can use whatever silly setup your mind can conjure.

A more lengthy description of accounts can be found in the banker's documentation.

    config.creatives.push_back(Creative(160, 600, "LeaderBoard"));
    config.creatives.push_back(Creative(300, 250, "BigBox"));
    config.creatives.push_back(Creative(728, 90,  "LeaderBoard"));

This bit sets up the format of the creatives that our agent is interesting to bid on. Any bid requests that doesn't contain at least one spot that fits one of these impression formats will be filtered out by the router and will never make it to our bidding agent. Less work for us, woo!

In addition to this mandatory filter, there are several other optional filters that can be specified:

  • hosts and urls

  • languages and locations

  • segments

  • exchanges

  • postion relative to the page's fold

  • hour of the week

  • user partitions

  • augmentation tags (see below)

      config.addAugmentation("frequency-cap-ex", Json::Value(42));
      config.augmentationFilter.include.push_back("pass-frequency-cap-ex");
    

Next, we tell the router that we'd be interested in having our bid requests augmented by our frequency cap example and set the frequency cap ceiling to 42. Since we don't want to see bid requests that haven't been augmented or that have hit our frequency cap ceiling, we also tell the router to filter out any bid request that doesn't contain the augmentation tag set by the frequency cap example.

In this case, we use the augmentors output purely for filtering but it's still possible to retrieve information injected by an augmentor while we're making our bid decision.

    doConfig(config.toJson());
}

Finally, we tell the world about our new shiny configuration using BiddingAgent's doConfig function. Note that an agent can change its configuration at any time by calling doConfig. This is useful to update certain filters like bidProbability which regulates the bid request stream that makes it to the agent.

Bidding Logic

blah

void bid(
        double timestamp,
        const Id & id,
        std::shared_ptr<RTBKIT::BidRequest> br,
        Bids bids,
        double timeLeftMs,
        const Json::Value & augmentations)
{
    for (Bid& bid : bids) {

blah

        ExcAssertEqual(bid.availableCreatives.size(), 1);
        int availableCreative = bid.availableCreatives.front();

blah

        (void) br->spots[bid.spotIndex];
        (void) config.creatives[availableCreative];

blah

        bid.bid(availableCreative, USD_CPM(2));
    }

blah

    Json::Value metadata = 42;

blah

    doBid(id, bids, metadata);
}

blah

Pacer

blah

bool accountSetup = false;

void pace()
{
    if (!accountSetup) {
        accountSetup = true;
        budgetController.addAccountSync(config.account);
    }

blah

    budgetController.topupTransferSync(config.account, USD(1));
}

blah

Putting It All Together

blah

struct FixedPriceBiddingAgent : public BiddingAgent {

blah

void init()
{
    onBidRequest = bind(
            &FixedPriceBiddingAgent::bid, this, _1, _2, _3, _4, _5, _6);

blah

    budgetController.init(getServices()->config);
    budgetController.start();

blah

    addPeriodic("FixedPriceBiddingAgent::pace", 10.0,
            [&] (uint64_t) { this->pace(); });

blah

    BiddingAgent::init();
}

blah

void start()
{
    BiddingAgent::start();
    setConfig();
}

blah

int main(int argc, char** argv)
{
    auto serviceProxies = args.makeServiceProxies();

blah

	RTBKIT::FixedPriceBiddingAgent agent(serviceProxies, "fixed-price-agent-ex");
	agent.init();
	agent.start();

	while (true) this_thread::sleep_for(chrono::seconds(10));
}

And that's it. You now a production ready-ish fixed price bidding agent. Welcome to a brave new world.

[^1]: RTBKit makes heavy use of C++11 so learn it and love it. Well makes heavy use of the subset of C++11 that gcc 4.7 supports.

Clone this wiki locally