Last active
February 1, 2019 09:33
-
-
Save takezoux2/2a804f89f2b4212b62ca15d2b7298e56 to your computer and use it in GitHub Desktop.
Adのウォーターフォールとヘッダービディングでどれくらい収益性が変わるかのシミュレーター
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.util._ | |
// ウォーターフォールとヘッダービディングの収益性の違いのシミュレーター | |
object Main { | |
def main(args: Array[String]): Unit = { | |
/*val r = new Random() | |
println((0 to 10000).count(i => Math.abs(r.nextGaussian()) < 1.96))*/ | |
println("完全なヘッダービディングの収益を100%とした場合") | |
val AdNetworkCount = 4 | |
val RepeatCount = 100000 | |
for { | |
average <- List(10,15,20) | |
adNetworkDiffRate <- List(0.1, 0.2, 0.3) | |
} { | |
implicit val generator = new RequestGenerator(average, AdNetworkCount, adNetworkDiffRate) | |
println(s"平均CPM:${average} AdNetworkばらつき率:${adNetworkDiffRate} AdNetwork数:${AdNetworkCount}") | |
WaterFall(LongWaterFall).simulate(RepeatCount).print("ウォーターフォール(長)") | |
WaterFall(MiddleWaterFall).simulate(RepeatCount).print("ウォーターフォール(中)") | |
WaterFall(ShortWaterFall).simulate(RepeatCount).print("ウォーターフォール(短)") | |
SemiHeaderBidding(average).simulate(RepeatCount).print("semiヘッダービディング") | |
println("-----") | |
} | |
/* 実行例 | |
完全なヘッダービディングの収益を100%とした場合 | |
平均CPM:10 AdNetworkばらつき率:0.1 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:98.45786001464806% | |
ウォーターフォール(中) 回収率:95.94250759968641% | |
ウォーターフォール(短) 回収率:95.32811642286117% | |
semiヘッダービディング 回収率:96.22654055843394% | |
----- | |
平均CPM:10 AdNetworkばらつき率:0.2 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:98.80534380288879% | |
ウォーターフォール(中) 回収率:94.85651925484314% | |
ウォーターフォール(短) 回収率:92.5891787886672% | |
semiヘッダービディング 回収率:93.48112730550136% | |
----- | |
平均CPM:10 AdNetworkばらつき率:0.3 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:99.02468882454988% | |
ウォーターフォール(中) 回収率:95.14958872449401% | |
ウォーターフォール(短) 回収率:91.47923209263206% | |
semiヘッダービディング 回収率:91.57295896824192% | |
----- | |
平均CPM:15 AdNetworkばらつき率:0.1 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:97.81008185174099% | |
ウォーターフォール(中) 回収率:96.41267007480255% | |
ウォーターフォール(短) 回収率:95.47571811468512% | |
semiヘッダービディング 回収率:96.29300628110845% | |
----- | |
平均CPM:15 AdNetworkばらつき率:0.2 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:98.12196441326212% | |
ウォーターフォール(中) 回収率:96.1872915214154% | |
ウォーターフォール(短) 回収率:93.3970807081184% | |
semiヘッダービディング 回収率:93.46957656577808% | |
----- | |
平均CPM:15 AdNetworkばらつき率:0.3 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:98.36494870911764% | |
ウォーターフォール(中) 回収率:96.61328990879493% | |
ウォーターフォール(短) 回収率:93.05973523451505% | |
semiヘッダービディング 回収率:91.55412645199016% | |
----- | |
平均CPM:20 AdNetworkばらつき率:0.1 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:96.95262705408607% | |
ウォーターフォール(中) 回収率:96.29636533609869% | |
ウォーターフォール(短) 回収率:95.514557836815% | |
semiヘッダービディング 回収率:96.24046117987854% | |
----- | |
平均CPM:20 AdNetworkばらつき率:0.2 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:96.54949591312048% | |
ウォーターフォール(中) 回収率:95.59217621549557% | |
ウォーターフォール(短) 回収率:93.44760157561835% | |
semiヘッダービディング 回収率:93.45394837685826% | |
----- | |
平均CPM:20 AdNetworkばらつき率:0.3 AdNetwork数:4 | |
ウォーターフォール(長) 回収率:96.26946117935829% | |
ウォーターフォール(中) 回収率:95.36890381258682% | |
ウォーターフォール(短) 回収率:93.0202725217402% | |
semiヘッダービディング 回収率:91.5592365847358% | |
----- | |
*/ | |
} | |
val LongWaterFall: List[Double] = List( | |
30, | |
25, | |
20, | |
17.5, | |
15, | |
14, | |
13, | |
12, | |
11, | |
10, | |
9, | |
8, | |
7, | |
6, | |
5, | |
3 | |
) | |
val MiddleWaterFall: List[Double] = List( | |
30, | |
25, | |
20, | |
15, | |
10, | |
5 | |
) | |
val ShortWaterFall: List[Double] = List( | |
30, | |
20, | |
10, | |
5 | |
) | |
} | |
case class ResultPrinter(best: Double, actual: Double) { | |
def print(name: String) = { | |
println(s"${name} 回収率:${(actual/best*100)}%") | |
} | |
} | |
case class WaterFall(floors: List[Double])(implicit generator: RequestGenerator) { | |
def simulate(count: Int) = { | |
val selectedECPMs = for(i <- 0 until count) yield { | |
val req = generator.next() | |
val best = req.cpms.max | |
val actual = choose(req) | |
//println(s"${actual}/${best} ${req}") | |
best -> actual | |
} | |
val (bests, actuals) = selectedECPMs.toList.unzip | |
//println(actuals.sum / bests.sum) | |
ResultPrinter(bests.sum, actuals.sum) | |
} | |
def choose(req: Request): Double = { | |
// ウォーターフォールの先頭からチェックし、 | |
// floorを満たすcpmを提供するAdNetworkが見つかればそのcpmを返す | |
for { | |
floor <- floors | |
cpm <- req.cpms | |
} { | |
if(floor <= cpm) { | |
return cpm | |
} | |
} | |
// Backfillに落ちたものは、ほとんどファーストのネットワークに取られると仮定 | |
req.cpms(0) * 0.8 + req.cpms(1) | |
} | |
} | |
class RequestGenerator(averageCpm: Double,numOfCompany: Int, companyDiffRate: Double) { | |
val random = new Random() | |
def next() = { | |
//val a = random.nextDouble * averageCpm * 2 | |
val a = Math.max(0.01, random.nextGaussian() / 1.96 * averageCpm + averageCpm) | |
Request((0 until numOfCompany).map(i => | |
a * (1 - companyDiffRate + random.nextDouble() * 2 * companyDiffRate) | |
).toList) | |
} | |
} | |
case class Request( | |
cpms: List[Double] | |
) | |
/* | |
始めのネットワークをある閾値以上の場合だけ優先して出し、それ以下のものの場合はヘッダービディングを行う | |
*/ | |
case class SemiHeaderBidding(firstFloor: Double)(implicit generator: RequestGenerator) { | |
def simulate(count: Int) = { | |
val selectedECPMs = for(i <- 0 until count) yield { | |
val req = generator.next() | |
val best = req.cpms.max | |
val actual = if(req.cpms(0) >= firstFloor) { | |
req.cpms(0) | |
} else { | |
req.cpms.max | |
} | |
//println(s"${actual}/${best} ${req}") | |
best -> actual | |
} | |
val (bests, actuals) = selectedECPMs.toList.unzip | |
//println(actuals.sum / bests.sum) | |
ResultPrinter(bests.sum, actuals.sum) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment