Compare commits
14 Commits
0a7dd54597
...
main
Author | SHA1 | Date | |
---|---|---|---|
faacb1d4a0
|
|||
52684daefe
|
|||
3695f2704b
|
|||
0b68cb80bf
|
|||
16f718dcdc
|
|||
049378518a
|
|||
a63608bac5
|
|||
0f0349810f
|
|||
91ebe9ddc3
|
|||
2ebc2f904f
|
|||
b83a3ed5c3
|
|||
117a5e9f2b
|
|||
d08ee55795
|
|||
6a90931c57
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
main/main.exe
|
139
auctionsim/summary.go
Normal file
139
auctionsim/summary.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package auctionsim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digits of precision in floating-point values in CSVs.
|
||||||
|
const CSVPrecision = 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResultSummary contains notable results of an auction.
|
||||||
|
*/
|
||||||
|
type ResultSummary struct {
|
||||||
|
/// The number of bidders in the auction.
|
||||||
|
Bidders int
|
||||||
|
/// The price the item sold for.
|
||||||
|
Price float64
|
||||||
|
/// The amount of value the winner actually got out of the auctioned item.
|
||||||
|
WinnerValue float64
|
||||||
|
/// The highest bid the auction winner was willing to make.
|
||||||
|
WinnerMaxBid float64
|
||||||
|
/**
|
||||||
|
* The amount by which the winner's value of the item exceeded the price
|
||||||
|
* they paid. Often negative.
|
||||||
|
*/
|
||||||
|
WinnerProfit float64
|
||||||
|
/**
|
||||||
|
* Number of losing bidders who had a "true value" of the item in excess
|
||||||
|
* of the price paid.
|
||||||
|
*/
|
||||||
|
LosersWithRegrets int
|
||||||
|
/// The most the item was genuinely worth to any bidder.
|
||||||
|
HighestValue float64
|
||||||
|
/// The most the bidder with the highest value would have been willing to pay.
|
||||||
|
HighestValuatorBid float64
|
||||||
|
/**
|
||||||
|
* How much the bidder with the highest value would have made in profit had
|
||||||
|
* they won the auction for the price the auction's actual winner paid.
|
||||||
|
* Often negative, indicating nobody would have made money on the auction.
|
||||||
|
* If this is zero or negative, there are 0 losers with regrets.
|
||||||
|
*/
|
||||||
|
MissedProfit float64
|
||||||
|
/**
|
||||||
|
* How much more valuable the item was to the bidder with the highest value
|
||||||
|
* than it was to the auction's eventual winner, irrespective of winning bid.
|
||||||
|
*/
|
||||||
|
ValueDelta float64
|
||||||
|
/**
|
||||||
|
* The rank of the bidder who actually had the highest value of the item.
|
||||||
|
* The winner of the auction has rank 1. If all bidders had a true value of
|
||||||
|
* negative infinity for the item, this will be greater than the number of
|
||||||
|
* bidders.
|
||||||
|
*/
|
||||||
|
HighestValueRank int
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarize takes an already-sorted list of bidders (see RunAuctionVerbosely)
|
||||||
|
* and the winning auction price, and calculates a ResultSummary for that auction.
|
||||||
|
*/
|
||||||
|
func Summarize(price float64, allBidders []Bidder) *ResultSummary {
|
||||||
|
regrets := 0
|
||||||
|
maxValue := math.Inf(-1)
|
||||||
|
maxIdx := -1
|
||||||
|
|
||||||
|
for i, b := range allBidders {
|
||||||
|
if b.Value > maxValue {
|
||||||
|
maxValue = b.Value
|
||||||
|
maxIdx = i
|
||||||
|
}
|
||||||
|
if b.Value > price {
|
||||||
|
regrets++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winner := allBidders[len(allBidders)-1]
|
||||||
|
rube := Bidder{Value: math.Inf(-1)}
|
||||||
|
if maxIdx >= 0 {
|
||||||
|
rube = allBidders[maxIdx]
|
||||||
|
}
|
||||||
|
return &ResultSummary{
|
||||||
|
Bidders: len(allBidders),
|
||||||
|
Price: price,
|
||||||
|
WinnerValue: winner.Value,
|
||||||
|
WinnerMaxBid: winner.BidCeiling(),
|
||||||
|
WinnerProfit: winner.Value - price,
|
||||||
|
LosersWithRegrets: regrets,
|
||||||
|
HighestValue: maxValue,
|
||||||
|
HighestValuatorBid: rube.BidCeiling(),
|
||||||
|
MissedProfit: rube.Value - price,
|
||||||
|
ValueDelta: maxValue - winner.Value,
|
||||||
|
HighestValueRank: len(allBidders) - maxIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResultSummaryCSVHeader returns column labels for the records emitted by
|
||||||
|
* (*ResultSummary).CSVRecord().
|
||||||
|
*/
|
||||||
|
func ResultSummaryCSVHeader() []string {
|
||||||
|
return []string{
|
||||||
|
"Bidders",
|
||||||
|
"Price",
|
||||||
|
"WinnerValue",
|
||||||
|
"WinnerMaxBid",
|
||||||
|
"WinnerProfit",
|
||||||
|
"LosersWithRegrets",
|
||||||
|
"HighestValue",
|
||||||
|
"HighestValuatorBid",
|
||||||
|
"MissedProfit",
|
||||||
|
"ValueDelta",
|
||||||
|
"HighestValueRank",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvFloat(f float64) string {
|
||||||
|
return strconv.FormatFloat(f, 'g', CSVPrecision, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSVRecord returns rows intended for use with encoding/csv.Writer, with
|
||||||
|
* columns in the order specified by ResultSummaryCSVHeader().
|
||||||
|
*/
|
||||||
|
func (s *ResultSummary) CSVRecord() []string {
|
||||||
|
return []string{
|
||||||
|
strconv.Itoa(s.Bidders),
|
||||||
|
csvFloat(s.Price),
|
||||||
|
csvFloat(s.WinnerValue),
|
||||||
|
csvFloat(s.WinnerMaxBid),
|
||||||
|
csvFloat(s.WinnerProfit),
|
||||||
|
strconv.Itoa(s.LosersWithRegrets),
|
||||||
|
csvFloat(s.HighestValue),
|
||||||
|
csvFloat(s.HighestValuatorBid),
|
||||||
|
csvFloat(s.MissedProfit),
|
||||||
|
csvFloat(s.ValueDelta),
|
||||||
|
strconv.Itoa(s.HighestValueRank),
|
||||||
|
}
|
||||||
|
}
|
143
main/main.go
143
main/main.go
@ -1,43 +1,130 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"git.chromaticdragon.app/kistaro/auctionsim/auctionsim"
|
"git.chromaticdragon.app/kistaro/auctionsim/auctionsim"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
var (
|
||||||
num := 1000
|
bidders = flag.Int("b", 1000, "Number of bidders in each auction.")
|
||||||
if len(os.Args) > 1 {
|
runs = flag.Int("r", 1, "Number of auctions to run.")
|
||||||
n, err := strconv.ParseInt(os.Args[1], 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("can't parse %q as a number of bidders: %v", os.Args[1], err)
|
|
||||||
}
|
|
||||||
num = n
|
|
||||||
}
|
|
||||||
if num <= 1 {
|
|
||||||
log.Fatalf("can't run an auction with %d bidders", num)
|
|
||||||
}
|
|
||||||
|
|
||||||
price, bidder := auctionsim.RunAuction(
|
errorDev = flag.Float64("d", 1.0, "Standard deviation of bidder irrationality.")
|
||||||
auctionsim.CappedBidderGenerator{
|
errorMean = flag.Float64("m", 0.0, "Mean of bidder irrationality. Negative values represent a bias towards underbidding.")
|
||||||
G: auctionsim.NormalestBidderGenerator(),
|
|
||||||
Lim: num,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
delta := bidder.Value - price
|
format = flag.String("f", "txt", "Output format: plain text (\"txt\") or CSV (\"csv\").")
|
||||||
valueStr := "gaining nothing"
|
)
|
||||||
|
|
||||||
|
func deltaStr(delta float64) string {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
valueStr = fmt.Sprintf("gaining ¤%f", delta)
|
return fmt.Sprintf("gained ¤%f", delta)
|
||||||
}
|
}
|
||||||
if delta < 0 {
|
if delta < 0 {
|
||||||
valueStr = fmt.Sprintf("losing ¤%f", -delta)
|
return fmt.Sprintf("lost ¤%f", -delta)
|
||||||
|
}
|
||||||
|
return "broken even"
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFormat() error {
|
||||||
|
*format = strings.ToLower(*format)
|
||||||
|
if *format == "txt" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if *format == "csv" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unrecognized format: %q", *format)
|
||||||
|
}
|
||||||
|
|
||||||
|
type emitter interface {
|
||||||
|
Emit(*auctionsim.ResultSummary)
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
type textEmitter struct {
|
||||||
|
notFirst bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *textEmitter) Emit(summary *auctionsim.ResultSummary) {
|
||||||
|
if t.notFirst {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("-----------------")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
t.notFirst = true
|
||||||
|
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)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
if summary.LosersWithRegrets < 1 {
|
||||||
|
fmt.Println("The item was not worth that to anybody.")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("The item was worth that to %d bidders, who stopped bidding too soon.\n", summary.LosersWithRegrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("The auction winner paid ¤%f, %s.\n", price, valueStr)
|
fmt.Println()
|
||||||
fmt.Printf("The item was worth ¤%f to them.\n", bidder.Value)
|
if summary.HighestValueRank > summary.Bidders {
|
||||||
fmt.Printf("They would have paid up to ¤%f for it.\n", bidder.BidCeiling())
|
fmt.Println("Wow! It was infinitely beyond worthless to everybody.")
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user