2023-11-19 00:50:53 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-11-19 02:53:13 +00:00
|
|
|
"encoding/csv"
|
|
|
|
"flag"
|
2023-11-19 00:50:53 +00:00
|
|
|
"fmt"
|
2023-11-19 00:51:19 +00:00
|
|
|
"git.chromaticdragon.app/kistaro/auctionsim/auctionsim"
|
2023-11-19 00:50:53 +00:00
|
|
|
"log"
|
2023-11-19 02:53:13 +00:00
|
|
|
"math"
|
|
|
|
"math/rand"
|
2023-11-19 00:50:53 +00:00
|
|
|
"os"
|
2023-11-19 02:53:13 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
bidders = flag.Int("b", 1000, "Number of bidders in each auction.")
|
|
|
|
runs = flag.Int("r", 1, "Number of auctions to run.")
|
|
|
|
|
|
|
|
errorDev = flag.Float64("d", 1.0, "Standard deviation of bidder irrationality.")
|
|
|
|
errorMean = flag.Float64("m", 0.0, "Mean of bidder irrationality. Negative values represent a bias towards underbidding.")
|
|
|
|
|
|
|
|
format = flag.String("f", "txt", "Output format: plain text (\"txt\") or CSV (\"csv\").")
|
2023-11-19 00:50:53 +00:00
|
|
|
)
|
|
|
|
|
2023-11-19 01:27:50 +00:00
|
|
|
func deltaStr(delta float64) string {
|
|
|
|
if delta > 0 {
|
|
|
|
return fmt.Sprintf("gained ¤%f", delta)
|
|
|
|
}
|
|
|
|
if delta < 0 {
|
|
|
|
return fmt.Sprintf("lost ¤%f", -delta)
|
|
|
|
}
|
|
|
|
return "broken even"
|
|
|
|
}
|
|
|
|
|
2023-11-19 02:53:13 +00:00
|
|
|
func normalizeFormat() error {
|
|
|
|
*format = strings.ToLower(*format)
|
|
|
|
if *format == "txt" {
|
|
|
|
return nil
|
2023-11-19 00:50:53 +00:00
|
|
|
}
|
2023-11-19 02:53:13 +00:00
|
|
|
if *format == "csv" {
|
|
|
|
return nil
|
2023-11-19 00:50:53 +00:00
|
|
|
}
|
2023-11-19 02:53:13 +00:00
|
|
|
return fmt.Errorf("unrecognized format: %q", *format)
|
|
|
|
}
|
2023-11-19 00:50:53 +00:00
|
|
|
|
2023-11-19 02:53:13 +00:00
|
|
|
type emitter interface {
|
|
|
|
Emit(*auctionsim.ResultSummary)
|
|
|
|
Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
type textEmitter struct {
|
|
|
|
notFirst bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *textEmitter) Emit(summary *auctionsim.ResultSummary) {
|
2023-11-19 02:54:39 +00:00
|
|
|
if t.notFirst {
|
2023-11-19 02:53:13 +00:00
|
|
|
fmt.Println()
|
|
|
|
fmt.Println("-----------------")
|
|
|
|
fmt.Println()
|
|
|
|
}
|
2023-11-19 02:54:39 +00:00
|
|
|
t.notFirst = true
|
2023-11-19 02:19:00 +00:00
|
|
|
fmt.Printf("The auction winner paid ¤%f. They have %s.\n", summary.Price, deltaStr(summary.WinnerProfit))
|
|
|
|
fmt.Printf("The item was worth ¤%f to them.\n", summary.WinnerValue)
|
|
|
|
fmt.Printf("They would have paid up to ¤%f for it.\n", summary.WinnerMaxBid)
|
2023-11-19 01:27:50 +00:00
|
|
|
|
|
|
|
fmt.Println()
|
2023-11-19 02:19:00 +00:00
|
|
|
if summary.LosersWithRegrets < 1 {
|
|
|
|
fmt.Println("The item was not worth that to anybody.")
|
2023-11-19 01:27:50 +00:00
|
|
|
} else {
|
2023-11-19 02:19:00 +00:00
|
|
|
fmt.Printf("The item was worth that to %d bidders, who stopped bidding too soon.\n", summary.LosersWithRegrets)
|
2023-11-19 00:50:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-19 01:27:50 +00:00
|
|
|
fmt.Println()
|
2023-11-19 02:19:00 +00:00
|
|
|
if summary.HighestValueRank > summary.Bidders {
|
2023-11-19 01:27:50 +00:00
|
|
|
fmt.Println("Wow! It was infinitely beyond worthless to everybody.")
|
|
|
|
} else {
|
2023-11-19 02:19:00 +00:00
|
|
|
fmt.Printf("The bidder who would have gotten the most value was outbid by %d bidders.\n", summary.HighestValueRank-1)
|
|
|
|
fmt.Printf("The item was worth at most ¤%f to them. Their maximum bid was ¤%f.\n", summary.HighestValue, summary.HighestValuatorBid)
|
|
|
|
fmt.Printf("If they had paid the winning bid, they would have %s.\n", deltaStr(summary.MissedProfit))
|
|
|
|
fmt.Printf("They valued it ¤%f more than the winning bidder.\n", summary.ValueDelta)
|
2023-11-19 01:27:50 +00:00
|
|
|
}
|
2023-11-19 00:50:59 +00:00
|
|
|
}
|
2023-11-19 02:53:13 +00:00
|
|
|
|
|
|
|
func (*textEmitter) Flush() {}
|
|
|
|
|
|
|
|
type csvEmitter struct {
|
|
|
|
w *csv.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *csvEmitter) Emit(summary *auctionsim.ResultSummary) {
|
|
|
|
c.w.Write(summary.CSVRecord())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *csvEmitter) Flush() {
|
|
|
|
c.w.Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if err := normalizeFormat(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
e := emitter(&textEmitter{})
|
|
|
|
if *format == "csv" {
|
|
|
|
w := csv.NewWriter(os.Stdout)
|
|
|
|
e = &csvEmitter{w}
|
|
|
|
w.Write(auctionsim.ResultSummaryCSVHeader())
|
|
|
|
}
|
|
|
|
defer e.Flush()
|
|
|
|
|
|
|
|
for i := 0; i < *runs; i++ {
|
|
|
|
g := &auctionsim.BiddersFromDistributions{
|
|
|
|
Values: auctionsim.NormalestDistribution(),
|
|
|
|
Irrationalities: &auctionsim.NormalDistribution{
|
|
|
|
Rand: rand.New(rand.NewSource(int64(rand.Uint64()))),
|
|
|
|
StdDev: *errorDev,
|
|
|
|
Mean: *errorMean,
|
|
|
|
},
|
|
|
|
Bankrolls: auctionsim.ConstDistribution(math.Inf(1)),
|
|
|
|
}
|
|
|
|
e.Emit(auctionsim.Summarize(auctionsim.RunAuctionVerbosely(
|
|
|
|
&auctionsim.CappedBidderGenerator{
|
|
|
|
G: g,
|
|
|
|
Lim: int64(*bidders),
|
|
|
|
},
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|