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;
// }
}
Updated 3 days ago