Skip to content

Instantly share code, notes, and snippets.

@takezoux2
Last active February 1, 2019 09:33
Show Gist options
  • Save takezoux2/2a804f89f2b4212b62ca15d2b7298e56 to your computer and use it in GitHub Desktop.
Save takezoux2/2a804f89f2b4212b62ca15d2b7298e56 to your computer and use it in GitHub Desktop.
Adのウォーターフォールとヘッダービディングでどれくらい収益性が変わるかのシミュレーター
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