Dynamic bid floors in LevelPlay

Since the transition to real time bidding, ad monetisation managers have lost control and visibility in their waterfalls. Setting floor prices is the mechanism to optimise bidding networks and deliver ad revenue uplift to partners. Nefta has seen 10-25% ad revenue increase with a comprehensive use of bid floor prices. In order to effectively set floor prices, partners need advanced user value estimation models and market price or bid landscape models.

Setup in LevelPlay

LevelPlay enables partners to dynamically set bid floor prices using the "Price limitations using waterfall configuration". For every ad opportunity, partners should create WaterfallConfiguration with the floor parameter set. Request an ad with the floor price set, if no ad is returned, clear the waterfall configuration and request an ad without a floor price set.

Receive the dynamic bid floor for the user from Nefta

Firstly, integrate user insights and request the following user insight values:

  • Predicted floor price per user per ad format: calculated_user_floor_price_banner, calculated_user_floor_price_interstitial, calculated_user_floor_price_rewarded.

Final process

Using the bid_floor_price calculated above, set the WaterfallConfiguration floor parameter to the bid floor price.

No fill using the bid floor

Request an ad with the floor price set, if no ad is returned, clear the waterfall configuration and request an ad without a floor price set.

Example code

Below is an example showing the integration of dynamic floors in LevelPlay (not production-ready) .

private bool IsNoFill(int errorCode)
{
  return errorCode == 509 || errorCode == 606 || errorCode == 706 || errorCode == 1058 || errorCode == 1158;
}

private double _bidFloor;
private double _calculatedBidFloor;
private int _bidNoFillCount;

public void SetInsights(Dictionary<string, Insight> insights)
{
        
  _calculatedBidFloor = insights["calculated_user_floor_price_banner"]._float;
  _bidFloor = _calculatedBidFloor;

  var configuration = WaterfallConfiguration.Builder()
    .SetFloor(_bidFloor)
    .Build();
  IronSource.Agent.SetWaterfallConfiguration(configuration, AdFormat.Banner);
}

private void LoadAd()
{
  _banner = new LevelPlayBannerAd("vpkt7...yfwr4", LevelPlayAdSize.BANNER, LevelPlayBannerPosition.TopCenter, null, true, true);
  _banner.OnAdLoaded += OnAdLoaded;
  _banner.OnAdLoadFailed += OnAdLoadFailed;

  _banner.LoadAd();
}

private void OnAdLoadFailed(LevelPlayAdError error)
{
  Adapter.OnExternalAdFail(Adapter.AdType.Banner, _calculatedBidFloor, error.ErrorCode);
  
  if (IsNoFill(error.ErrorCode) && _bidFloor > 0)
  {
    _bidNoFillCount++;
        
    if (_bidNoFillCount == 1)
    {
      _bidFloor *= 0.95; // This is a 5% reduction for the 2nd call if first call for ad delivers no fill. this value can/should change over time.
      var configuration = WaterfallConfiguration.Builder()
        .SetCeiling(_bidFloor * 1.5)
        .SetFloor(_bidFloor)
        .Build();
      IronSource.Agent.SetWaterfallConfiguration(configuration, AdFormat.Banner);

      LoadAd();
    }
    else if (_bidNoFillCount == 2)
    {
      _bidFloor = 0;
      IronSource.Agent.SetWaterfallConfiguration(WaterfallConfiguration.Empty(), AdFormat.Banner);

      LoadAd();
    }
  }
}

private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
  Adapter.OnExternalAdLoad(Adapter.AdType.Banner, _calculatedBidFloor);
  
  _bidNoFillCount = 0
  // might try increase the bid again
  // if (_bidFloor < _calculatedBidFloor)
  // {
  //   _bidFloor = _calculatedBidFloor;
  // }
}