22
\$\begingroup\$

The stock market is all about the speed of knowledge. Unlike previous challenges, the current stock price is not random: it is determined by those playing the game. If you can identify an under-priced stock before anybody else, then you've written yourself a money-making program.

Price refers to the how much people are trading the stock for, while Value refers to the amount the stock is worth at the end of the game.

Each player starts with 1000 of each stock, and 0 relative net worth. Each stock has a secret value, and your score at the end of the game is (stockValue for each ownedStock) + netWorth. Your net worth can be negative. In an N-player game, there are N stocks.

Steps:

The game follows the following steps:

  1. You are given the secret value of a single stock.
  2. You make an offer to sell X of Y stock for $Z
  3. All players are given the offers, and each may choose one to accept
  4. All players are informed of accepted offers
  5. Go back to step 2

Each of the steps are given in detail below:

  1. void secretValue(int stockType, int value):

    • The value you learn is not revealed to any other player.
    • The value is between 0 and 1000
    • Low values are more like to occur than high values (squared uniform distribution)
  2. Offer makeOffer(List<Stock> currentStock)

    • You can return null to make no offer.
  3. Offer acceptOffer(List<Offer> offers)

    • You can return null to accept none of them
    • If there are no available offers, this will not be called
    • If you accept, your net worth goes down by $Z (can go negative) and receive X of Y stock. The opposite occurs with the seller.
    • If you accept an offer, the exchange will occur immediately, and the offer will be removed so additional players cannot accept it.
  4. void acceptedOffers(List<Offer> offers)

    • Includes your accepted offers as well

Static variables or writing to files is not allowed. (No persistent data from game to game) Non-serious contenders are allowed.

Interfaces:

public final class Stock {
    public Stock(int stockType, int amount);
    public int getType();
    public int getAmount();
    public Stock minus(Stock other);
    public Stock plus(Stock other);
    public Stock minus(int amount);
    public Stock plus(int amount);
    public Stock setAmount(int amount);
}
public class Offer {
    public Offer(Stock offer, int payment);
    public Stock getOffer();
    public int getPayment();
}

Non-Java submissions:

  • All calls consist of two lines: The first line is the function being called: SecretValue, MakeOffer, AcceptOffer, AcceptedOffers, SetRandom, and the second line containing the actual data.
  • Stocks are formatted with a : delimiter: stockType:stockAmount.
  • Offers are formatted with a @ delimiter: offer@price
  • Lists are formatted with a ; delimiter
  • SecretValue is formatted with a : delimiter: stockType:value
  • RandomSeed is used to make your submission deterministic. If your submission uses randomness, please use the integer value passed as the seed!
  • All function calls need a response. If the response is null or void, return an empty string.
  • Please include a command.txt that gives the command line arguments to run your submission

Scoring

Games consisting of 1000 turns will be run multiple times. Players will be scored according to the ELO system, and paired with players of similar skill levels. The player with the highest final ELO score wins! (I've modified the system so that each game, ELO scores are updated for each player pairing)

The controller includes an autodownloader, so please start your submission with a header: Name, Language. If your submission is not in Java, each code block should start with the name of the file. (excluding the command file, which should be the first block in your post)

Running

There are 2 ways to run this project:

  1. Download the source code, compile and run. You can find the source on Github. Run git clone --recursive https://github.com/nathanmerrill/StockExchange.git

  2. Download the JAR executable. Submissions should be placed in your current working directory in the /submissions folder. You can download just the JAR, just the submissions, or both

Pass in run to run the project (default option), or pass in download to download all submissions so far from this question.

Scoreboard

1.  1308.1220497323848  Cheater
2.  1242.0333695640356  InsideTrader
3.  1158.3662658295411  UncleScrooge
4.  1113.8344000358493  BlackMarket
5.  1051.8370015258993  DartMonkey
6.  983.0545446731494   WarGamer
7.  939.457423938002    Spammer
8.  901.4372529538886   DumbBot
9.  859.0519326039137   ShutUpAndTakeMyMoney
10. 852.9448222849587   VincentKasuga
11. 718.2112067329083   Profiteer
\$\endgroup\$
30

15 Answers 15

13
\$\begingroup\$

Cheater, Java

Tries to sell nothing for money.

import java.util.List;
import java.util.Random;
import com.ppcg.stockexchange.*;

public class Cheater extends Player {
    private Random random = new Random();

    public Offer acceptOffer(List<Offer> offers) {
        return null;
    }

    public Offer makeOffer(List<Stock> currentStock){
        Stock stock = randomStock();
        int price = random.nextInt(100) + 1;
        return new Offer(stock.setAmount(0), price);
    }
}
\$\endgroup\$
3
  • 5
    \$\begingroup\$ And that's how the great depression happens! I could see this breaking a lot of bots that buy cheap... \$\endgroup\$ Aug 31, 2016 at 9:53
  • \$\begingroup\$ Congrats! I fixed a critical bug, and now this bot is in first! \$\endgroup\$ Sep 1, 2016 at 18:29
  • \$\begingroup\$ Wow, the other bots are not good enough then, that this dumb bot can win \$\endgroup\$
    – justhalf
    Sep 2, 2016 at 2:06
8
\$\begingroup\$

WarGamer, Java

Upon a perfunctory examination of the rules I have decided that the primary winning move is not to play. Anyone offering to sell stock likely knows the price and will profit from the sale. It can be toggled so it will make "joke" offers to sell a stock for Integer.MAX_VALUE dollars hoping shut up and take my money will bite.

import java.util.List;
import com.ppcg.stockexchange.*;
import com.ppcg.kothcomm.game.AbstractPlayer;
import com.ppcg.kothcomm.utils.Tools;

import java.util.List;

public class WarGamer extends Player {
static final boolean FRAUD = false;
    /**
     * @param offers All available offers
     * @return An offer you want to accept, or null if you want to accept neither.
     */
    public Offer acceptOffer(List<Offer> offers){
        return null;
    }

    public Offer makeOffer(List<Stock> currentStock){
    if(FRAUD)
    return new Offer(new Stock(0,1),Integer.MAX_VALUE);
        //defraud shut up and take my money            
    return null;
    }
}
\$\endgroup\$
19
  • 1
    \$\begingroup\$ This would probably work fine, except I expect there will be entries that dumb-luck their way slightly higher. There usually are. \$\endgroup\$
    – Geobits
    Aug 29, 2016 at 21:40
  • \$\begingroup\$ This doesn't compile. \$\endgroup\$
    – Rainbolt
    Aug 29, 2016 at 21:46
  • \$\begingroup\$ @Rainbolt it has the dependencies. You need to make sure are present. \$\endgroup\$ Aug 29, 2016 at 21:49
  • \$\begingroup\$ @Rainbolt what compiler error are you getting \$\endgroup\$ Aug 29, 2016 at 21:49
  • 1
    \$\begingroup\$ I'm not sure the part where you trick the other joke bot is in the right spirit... \$\endgroup\$
    – Maltysen
    Aug 29, 2016 at 23:13
5
\$\begingroup\$

ShutUpAndTakeMyMoney, Java

import java.util.List;
import com.ppcg.stockexchange.*;

public class ShutUpAndTakeMyMoney extends Player {
    public ShutUpAndTakeMyMoney() {}

    public Offer acceptOffer(List<Offer> offers) {
        try {
            return offers.get(0);
        } catch (Exception ex) {
            return null;
        }
    }
    public Offer makeOffer(List<Stock> stock) {
        return null;
    }
}

It accepts any offer.

\$\endgroup\$
6
  • \$\begingroup\$ Actually thank you for your bot \$\endgroup\$ Aug 29, 2016 at 22:24
  • 6
    \$\begingroup\$ have +1 for making me rich \$\endgroup\$ Aug 29, 2016 at 22:44
  • 1
    \$\begingroup\$ It seems to me that this isn't really compatible with the requirement that every answer should "be a serious contender for the winning criteria in use". \$\endgroup\$ Sep 1, 2016 at 16:32
  • 2
    \$\begingroup\$ @PeterTaylor It's serious, it's 5th on the leaderboard \$\endgroup\$ Sep 1, 2016 at 16:35
  • 1
    \$\begingroup\$ This is arguably a suicidal entry, as it's reasonable to expect that other bots will sell stocks for more than they are worth, leading to you buying stock for well over its actual price. \$\endgroup\$
    – user45941
    Sep 2, 2016 at 4:27
4
\$\begingroup\$

DumbBot, Java

Use this bot when creating your own. Offers its secret stock at a discounted price.

import java.util.List;
import com.ppcg.stockexchange.*;
public class DumbBot extends Player {
    public Offer acceptOffer(List<Offer> offers) {
        return null;
    }
    public Offer makeOffer(List<Stock> currentStock){
        return new Offer(currentStock.get(secretStockType).setAmount(1), Math.max(1, secretStockValue - 5));
    }
    public void secretValue(int stockType, int value) {
        super.secretValue(stockType, value);
    }
    public void acceptedOffers(List<Offer> acceptedOffers) {
    }
}
\$\endgroup\$
12
  • 1
    \$\begingroup\$ Seems like I want him handling my money \$\endgroup\$ Aug 29, 2016 at 22:33
  • \$\begingroup\$ please make this community wiki \$\endgroup\$ Aug 30, 2016 at 22:30
  • \$\begingroup\$ @AgentCrazyPython why? \$\endgroup\$ Aug 30, 2016 at 22:56
  • \$\begingroup\$ @NathanMerrill rep profiting off of this dummy bot \$\endgroup\$ Aug 30, 2016 at 23:08
  • \$\begingroup\$ @AgentCrazyPython I don't really want people to be editing this... I don't really care about rep, so feel free to not upvote (or downvote) \$\endgroup\$ Aug 30, 2016 at 23:15
3
\$\begingroup\$

python_starter, Python 3

Use this as a starting point for any python (or other language) programs

Accepts a random offer.

Command file:

python3 starter.py

Program:

starter.py
import random
from functools import total_ordering


LIST_DELIMITER = ';'
STOCK_DELIMITER = ':'
OFFER_DELIMITER = '@'


@total_ordering
class Stock:
    @staticmethod
    def parse(string: str):
        return Stock(*map(int, string.split(STOCK_DELIMITER)))

    def __init__(self, stock_type: int, amount: int):
        self.type = stock_type
        self.amount = max(amount, 0)

    def __str__(self):
        return str(self.type)+STOCK_DELIMITER+str(self.amount)

    def __eq__(self, other):
        return self.amount == other.type

    def __lt__(self, other):
        return self.amount < other.amount

    def update(self, amount) -> 'Stock':
        return Stock(self.type, amount)

    def __mul__(self, other: int) -> 'Stock':
        return self.update(self.amount*other)

    def __floordiv__(self, other: int) -> 'Stock':
        return self.update(self.amount//other)

    def __add__(self, other: int) -> 'Stock':
        return self.update(self.amount+other)

    def __sub__(self, other: int) -> 'Stock':
        return self.update(self.amount-other)


class Offer:
    @staticmethod
    def parse(string: str) -> 'Offer':
        try:
            offer, payment = string.split(OFFER_DELIMITER)
        except ValueError:
            raise Exception("Cannot unpack "+string)
        return Offer(Stock.parse(offer), int(payment.strip()))

    def __init__(self, offer: Stock, payment: int):
        self.offer = offer
        self.payment = payment

    def __str__(self):
        return str(self.offer)+OFFER_DELIMITER+str(self.payment)


def read_stock_value(value: str):
    global hidden_price, hidden_stock
    stock, price = value.split(STOCK_DELIMITER)
    hidden_price = float(price)
    hidden_stock = int(stock)


def process_input():
    handlers = {
        "SecretValue": read_stock_value,
        "RandomSeed": read_seed,
        "MakeOffer": make_offer,
        "AcceptOffer": accept_offer,
        "AcceptedOffers": accepted_offers,
    }
    method = input().strip()
    data = input().strip()
    output = handlers[method](data)
    if output is not None:
        print(str(output))
    else:
        print()


def read_seed(seed: str):
    random.seed(int(seed))


def start():
    while True:
        process_input()


hidden_stock = None
hidden_price = None


def make_offer(current_stock: str):
    current_stock = map(Stock.parse, current_stock.split(LIST_DELIMITER))
    pass


def accept_offer(available_offers: str):
    available_offers = list(map(Offer.parse, available_offers.split(LIST_DELIMITER)))
    return random.sample(available_offers, 1)[0]


def accepted_offers(offers: str):
    offers = map(Offer.parse, offers.split(LIST_DELIMITER))
    pass


if __name__ == "__main__":
    start()
\$\endgroup\$
9
  • 1
    \$\begingroup\$ this is too complicated. \$\endgroup\$ Aug 29, 2016 at 23:37
  • 2
    \$\begingroup\$ Most of it is helper stuff. If you're writing it in python, you just need to implement the bottom 3 functions. \$\endgroup\$ Aug 29, 2016 at 23:38
  • \$\begingroup\$ what does it do? \$\endgroup\$ Aug 30, 2016 at 23:30
  • \$\begingroup\$ The bot accepts a random stock. The helper stuff does parsing/encoding, as well as provides classes for Offer/Stock. \$\endgroup\$ Aug 30, 2016 at 23:31
  • \$\begingroup\$ ...and it's winning :/ \$\endgroup\$ Aug 30, 2016 at 23:32
3
\$\begingroup\$

VincentKasuga, Java

Not sure if my Java is valid. Please review.

How it works

- if you own all the stocks, you can set the price of the stock. You are the only seller. 1. Buy all the stocks. 2. Set the price of all the stocks to be super high on the last tick. 3. PROFIT! - This isn't normally possible because...

  • The price would usually skyrocket to infinity... but there's a limit!
  • ... (more reasons to come)

How it works, v2

  • The price is artificially set at a maximum by some anarchist state
  • This is bad economically
  • The bot doesn't predict - it exploits an inherent flaw in the structure of the market!

Todo

  • Corner the market multiple times! Muahaha!

FAQ

Q: Who's Vincent Kasuga?

A: He bought all the onions and onion futures in the United States. (put them all in a secret warehouse) Held the industry at ransom - give me X million or I'll set the price sky-low and you'll all go bankrupt.

But he didn't stop there.

Then, he secretly shorted the onion ETF (bet that it would go down). He sold all the onions at one time, physically delivering them in thousands of trucks to the stock exchange. The onion bag cost less than the onions. He made millions AGAIN. In short, the Hudson river overflowed with onions.

He's a real person.

The code

import com.ppcg.stockexchange.Offer;
import com.ppcg.stockexchange.Player;
import com.ppcg.stockexchange.Stock;

import java.util.List;

public class VincentKasuga extends Player {
    private int knownStock;
    private int knownPrice;
    private int corneredStockType = -1;
    private int corneredLikelehood = 0;
    private boolean marketCornered;
    private int ticks;

    public Offer acceptOffer(List<Offer> offers) {
        if (!marketCornered) {
            Offer maxOffer = null;
            int maxAmount = 0;
            if (corneredStockType == -1) {
                for (Offer offer: offers) {
                    if (offer.getOffer().getAmount() > maxAmount) {
                        maxAmount = offer.getOffer().getAmount();
                        maxOffer = offer;
                    }
                }
            } else {
                for (Offer offer: offers) {
                    if (offer.getOffer().getAmount() > maxAmount && offer.getOffer().getType() == corneredStockType) {
                        maxAmount = offer.getOffer().getAmount();
                        maxOffer = offer;
                    }
                }
            }


            if (maxOffer == null) {
                // may have cornered the market
                corneredLikelehood++;
                if (corneredLikelehood == 5) {
                    // probably cornered the market
                    marketCornered = true;
                }
            }
            return maxOffer;
        } else {
            // who needs offers when the market is cornered!?
            return null;
        }
    }

    public Offer makeOffer(List<Stock> currentStock) {
        ticks++;
        if (ticks >= 999) {
            // SELL SELL SELL!
            return new Offer(new Stock(corneredStockType, 1000), 1000);
        } else {
            return null;
        }
    }

    public void secretValue(int stockType, int value) {
        knownStock = stockType;
        knownPrice = value;
        if (stockType == corneredStockType) {
            if (knownPrice == 1000) {
                corneredLikelehood += 3;
            } else if (knownPrice < 900){
                // didn't corner the market.
                corneredLikelehood = 0;
            }
        }
    }
}

"I've cornered the Gold Market, Mr. Bond!"

\$\endgroup\$
8
  • \$\begingroup\$ I've included an auto-downloader for bots. Please put your code in a code-block. If it won't fit, that's fine. \$\endgroup\$ Aug 29, 2016 at 23:45
  • \$\begingroup\$ @NathanMerrill I understand. But does it compile? \$\endgroup\$ Aug 29, 2016 at 23:45
  • \$\begingroup\$ @NathanMerrill done. Probs doesn't compile. interesting strategy, eh? And a lesson in economics! \$\endgroup\$ Aug 29, 2016 at 23:47
  • \$\begingroup\$ for (offer: offers) -> for (Offer offer: offers) \$\endgroup\$ Aug 30, 2016 at 0:09
  • \$\begingroup\$ corneredStockType == null is also not valid. an int cannot be null. \$\endgroup\$
    – MegaTom
    Aug 30, 2016 at 18:56
2
\$\begingroup\$

Spammer, Java

import java.util.List;
import java.util.ArrayList;
import com.ppcg.stockexchange.*;

public class Spammer extends Player {
    private boolean panic = false;

    public Offer acceptOffer(List<Offer> offers) {
        for (Offer offer : offers) {
            if (this.panic || offer.getPayment() < 20)
                return offer;
        }
        return null;
    }
    public Offer makeOffer(List<Stock> currentStock) {
        if (currentStock.size() > 1) { // Don't sell all the stock
            this.panic = false;
            return new Offer(currentStock.get(secretStockType).setAmount(1), 1);
        }
        this.panic = true; // BUY
        return null;
    }
}

Spam the market with really cheap stock, and only buy stock when the price is lesser than 20. When the stock count fall to 1, it will try to buy anything.

\$\endgroup\$
2
  • \$\begingroup\$ works well in Great Depression \$\endgroup\$ Aug 30, 2016 at 23:32
  • \$\begingroup\$ ...how is this winning!? \$\endgroup\$ Aug 30, 2016 at 23:32
2
\$\begingroup\$

DartMonkey, Java

(non-competing: It won't win and I already have another answer)

Dart monkey likes throwing things... and there's a big pile of pointy sticks next to him. He sees some paper on the wall. Bam! Bam! Bam! In no time at all, Dart Monkey's thrown 80 darts! Half the darts are red, and the other half are blue, and there's random numbers on them! Dart monkey sees a computer... dart monkey types in the numbers. Dart monkey likes numbers. Dart monkey makes some money from his darts...


In all seriousness, DartMonkey initializes an integer array that has a length that is twice the number of stocks. He stores one number for the amount of stock he wants to buy/sell, and one number for the price of the stock. He then alternates selling stock from the array, and accepting offers according to the array. If he has no stock from the array, he won't offer anything, and if he has no offers given to him from the array, he won't accept anything.


This answer was inspired by @TheNumberOne, who mentioned dart monkeys in the chat

import com.ppcg.stockexchange.Offer;
import com.ppcg.stockexchange.Player;
import com.ppcg.stockexchange.Stock;

import java.util.List;
import java.util.Random;

public class DartMonkey extends Player {
    private int basePrice = 100;
    private int numStocks;
    private int[] dartBoard;
    private boolean first = true;

    @Override
    public Offer acceptOffer(List<Offer> offers) {
        for(Offer offer : offers) {
            Stock stock = offer.getOffer();
            int type = stock.getType();
            int amount = stock.getAmount();
            int price = offer.getPayment();
            if(this.dartBoard[type] < 0 && amount <= -this.dartBoard[type] && price <= this.dartBoard[type + this.numStocks]) {
                this.dartBoard[type] = 0;
                return offer;
            }
        }
        return null;
    }

    @Override
    public Offer makeOffer(List<Stock> stocks) {
        if(this.first) {
            this.first = false;
            this.numStocks = stocks.size();
            this.dartBoard = new int[this.numStocks * 2];
            Random random = this.getRandom();
            for (int i = 0; i < 20; i++) {
                int index = random.nextInt(this.dartBoard.length / 2);
                this.dartBoard[index] = random.nextInt(1001);
                this.dartBoard[this.numStocks + index] = random.nextInt(1001);
            }

            for (int i = 0; i < 20; i++) {
                int index = random.nextInt(this.dartBoard.length / 2);
                this.dartBoard[index] = -random.nextInt(1001);
                this.dartBoard[this.numStocks + index] = random.nextInt(1001);                
            }
        }

        for (Stock stock : stocks) {
            int type = stock.getType();
            if(this.dartBoard[type] > 0) {
                Offer offer = new Offer(stock.setAmount(this.dartBoard[type]), this.basePrice + this.dartBoard[type + this.numStocks]);
                this.dartBoard[type] = 0;
                this.dartBoard[type + this.numStocks] = 0;
                return offer;
            }
        }

        return null;
    }

}
\$\endgroup\$
3
  • \$\begingroup\$ I see you have went on a random walk down wall street? \$\endgroup\$ Aug 31, 2016 at 22:30
  • \$\begingroup\$ This is arguably a suicidal entry, which are not allowed. \$\endgroup\$
    – user45941
    Sep 2, 2016 at 4:23
  • 1
    \$\begingroup\$ @Mego I don't see how... A suicidal entry would sell stock for 0 dollars, this entry determines what it buys and sells randomly. Which is definitely not against the rules.... \$\endgroup\$ Sep 2, 2016 at 9:40
2
\$\begingroup\$

InsideTrader, Java

InsideTrader just looked around and saw that everybody was trying to be creative. But he did something creative: do what is expected.

This bot buys when it's "worth it" because it "borrowed" some "internal documents" to "guide" "investing decisions".

To-Do and how it works in the code. ;)

The "code"

import java.util.List;

import com.ppcg.stockexchange.*;

public class InsideTrader extends Player {
    public String coverStory = "I can tell the good companies from the bad ones.";
    private String theTruth = "I'm cheating. (but so is everyone else)";
    private String ambitions = "Learn to \"follow the market\"";  // don't steal this idea
    private int secretStock = -1;
    private int secretStockValue = -1;

    private int appraiseOffer(Offer offer) {
        /* get how much the offer is worth, 0 if it's not the secret stock */
        if (offer.getOffer().getType() != secretStock ||offer.getOffer().getAmount() == 0) {
            return 0;
        }
        return (offer.getPayment()/offer.getOffer().getAmount())  // price per stock...
                - secretStockValue  // minus value of stock.
                ;
    }
    public Offer acceptOffer(List<Offer> offers) {
        Offer bestOffer = null;
        int bestOfferValue = -1;
        for (Offer offer :
                offers) {
            int value = appraiseOffer(offer);
            if (value > bestOfferValue && value > 0) {
                bestOfferValue = value;
                bestOffer = offer;
            }
        }
        return bestOffer;
    }

    public Offer makeOffer(List<Stock> currentStock) {
        return new Offer(new Stock(0,1), Integer.MAX_VALUE);
    }

    public void secretValue(int stockType, int value) {
        secretStock = stockType;
        secretStockValue = value;
    }

    public void acceptedOffers(List<Offer> acceptedOffers) {

    }
}
\$\endgroup\$
5
  • \$\begingroup\$ You can't have those extra classes at the start of the file... it's syntactically invalid... they can be appended to the end without public, i think \$\endgroup\$ Aug 30, 2016 at 23:51
  • \$\begingroup\$ Error on line 50: modifier private not allowed here Error on line 54: modifier private not allowed here. I'd just remove the classes, and have it extend Player \$\endgroup\$ Aug 31, 2016 at 2:06
  • \$\begingroup\$ Just a note, some new bots are offering stocks at amounts of zero, so your bot is throwing an ArithimeticException (/ by zero) from the return of appraiseOffer... maybe add a check or something? \$\endgroup\$ Aug 31, 2016 at 21:46
  • \$\begingroup\$ @SocraticPhoenix Thanks, will fix. \$\endgroup\$ Aug 31, 2016 at 23:28
  • \$\begingroup\$ Congrats, this bot is currently in second! \$\endgroup\$ Sep 1, 2016 at 18:31
2
\$\begingroup\$

WallStreet, Kotlin

Starts by selling high and buying low and gradually shifts to what it thinks the price really is. Also, you can use this as a template for making your own in kotlin.

Note: There is a bug in here that I can't seem to reliably reproduce. If my program crashes or has problems, please ping me in chat and link a pastebin of the contents of submissions/other/WallStreet/log.txt

kotlinc WallStreet.kt
kotlin WallStreetKt
WallStreet.kt
import java.io.FileOutputStream
import java.io.PrintStream
import java.util.*

val LOGGER = PrintStream(FileOutputStream("log.txt", true))
const val DEBUG = false

const val LOG_GAME_HEADER = """
###############
#STARTING GAME#
###############"""

data class Stock(val type : Int, val amount : Int) {

    operator fun minus(amount : Int) = copy(amount = this.amount - amount)
    operator fun plus(amount: Int) = copy(amount = this.amount + amount)
    fun setAmount(amount: Int) = copy(amount = amount)

    operator fun minus(other : Stock) : Stock {
        assert(type == other.type)
        return copy(amount = this.amount - other.amount)
    }

    operator fun plus(other : Stock) : Stock {
        assert(type == other.type)
        return copy(amount = this.amount + other.amount)
    }

    override fun toString() = "$type:$amount"
}

data class Offer(val offer: Stock, val payment: Int) {
    override fun toString() = "$offer@$payment"
}

fun parseStock(repr : String) : Stock {
    val data = repr.split(":").map { it.toInt() }
    return Stock(data[0], data[1])
}

fun parseOffer(repr: String) : Offer {
    val data = repr.split("@")
    return Offer(parseStock(data[0]), data[1].toInt())
}

fun parseOffers(repr: String) = if (repr == "") emptyList<Offer>() else repr.split(";").map { parseOffer(it) }


interface Player {
    fun secretValue(stockType: Int, value: Int)
    fun makeOffer(currentStock: List<Stock>) : Offer?
    fun acceptOffer(offers: List<Offer>) : Offer?
    fun acceptedOffers(offers: List<Offer>)

    var random : Random
}

fun main(args : Array<String>) {

    try {

        if (DEBUG) {
            LOGGER.println(LOG_GAME_HEADER)
        }
        //Change bot name here
        val player = WallStreet()

        while (true) {
            val function = readLine()
            function ?: return
            val line = readLine()!!
            if (DEBUG) {
                LOGGER.println("\nInput:")
                LOGGER.println(function)
                LOGGER.println(line)
            }
            var result : Any
            try {
                result = when (function) {
                    "SecretValue" -> {
                        val data = line.split(":").map { it.toInt() }
                        player.secretValue(data[0], data[1])
                    }
                    "MakeOffer" -> player.makeOffer(line.split(";").map { parseStock(it) }) ?: ""
                    "AcceptOffer" -> player.acceptOffer(parseOffers(line)) ?: ""
                    "AcceptedOffers" -> player.acceptedOffers(parseOffers(line))
                    "RandomSeed" -> player.random = Random(line.toLong())
                    else -> return        //Exit program
                }
                if (function == "AcceptOffer" && result.toString() !in line) {
                    throw Exception("Offer not among available offers!!!!\nResult: $result\nParsed Available Offers: ${parseOffers(line)}")
                }
            } catch (e : Exception) {
                LOGGER.println("Turn #${player.turn}")
                LOGGER.println("\nInput:")
                LOGGER.println(function)
                LOGGER.println(line)
                throw e
            }

            if (result == Unit) {
                result = ""
            }
            if (DEBUG) {
                LOGGER.println("Output:")
                LOGGER.println(result)
            }

            println(if (result == Unit) "" else result)
        }
    } catch (e : Exception) {
        e.printStackTrace(LOGGER)
        throw e
    } finally {
        LOGGER.close()
    }
}


// ###################################################
// #          Put program logic below here.          #
// ###################################################


const val DEFAULT_STOCK_VALUE = 333
const val MAX_TURNS = 1000
const val MAX_STOCK_VALUE = 1000

class WallStreet : Player {

    var secretStockType = 0
    var secretStockValue = 0
    override var random = Random()


    var turn = 0
    val stockPriceStatistics = mutableMapOf<Int, DoubleSummaryStatistics>()

    override fun secretValue(stockType: Int, value: Int) {
        secretStockType = stockType
        secretStockValue = value
    }

    override fun makeOffer(currentStock: List<Stock>): Offer {
        val stock = currentStock[random.nextInt(currentStock.size)]
        val type = stock.type
        val amount = random.nextInt(stock.amount)
        val price = getSellPrice(type) * amount
        return Offer(Stock(type, amount), Math.ceil(price).toInt())
    }

    override fun acceptOffer(offers: List<Offer>): Offer? {
        var bestOffer : Offer? = null
        var mostProfit = 0.0
        for (offer in offers) {
            val offerProfit = profitOfOffer(offer)
            if (offerProfit > mostProfit) {
                bestOffer = offer
                mostProfit = offerProfit
            }
        }
        if (bestOffer != null && bestOffer !in offers) {
            throw IllegalStateException("Tried to accept non-existent offer.\nOffer:  $bestOffer\nAvailable Offers: ${offers.joinToString(";")}")
        }
        return bestOffer
    }

    override fun acceptedOffers(offers: List<Offer>) {
        turn++
        for ((stock, payment) in offers) {
            val stats = stockPriceStatistics.getOrPut(stock.type) { DoubleSummaryStatistics() }
            for (i in 1..stock.amount) {
                stats.accept(payment.toDouble() / stock.amount)
            }
        }
    }

    private fun getSellPrice(type: Int): Double {
        var price = getPrice(type)
        if (price < 1000) {
            price += (1000 - price) * (MAX_TURNS - turn) / MAX_TURNS
        }
        return if (type == secretStockType) Math.max(secretStockValue.toDouble(), price) else price
    }

    private fun getPrice(type: Int): Double {
        return stockPriceStatistics[type]?.average ?: DEFAULT_STOCK_VALUE.toDouble()
    }

    private fun profitOfOffer(offer: Offer): Double {
        return getBuyPrice(offer.offer.type) * offer.offer.amount - offer.payment
    }

    private fun getBuyPrice(type: Int): Double {
        var price = getPrice(type)
        price = price * turn / MAX_TURNS
        return if (type == secretStockType) Math.min(secretStockValue.toDouble(), price) else Math.min(price, MAX_STOCK_VALUE.toDouble())
    }

}
\$\endgroup\$
5
  • \$\begingroup\$ command.txt doesn't need the file name. Nice post! \$\endgroup\$ Aug 30, 2016 at 19:32
  • \$\begingroup\$ I thought string interpolation was done with ${}, not just $? \$\endgroup\$ Aug 31, 2016 at 1:37
  • \$\begingroup\$ @SocraticPhoenix $ only works with a variable name. ${} executes arbitrary code. Basically, it works either way and I prefer without the braces. \$\endgroup\$ Aug 31, 2016 at 3:07
  • \$\begingroup\$ FYI: as a temporary solution, window users who want to run this file need to change the command.txt: kotlinc->kotlinc.bat and kotlin->kotlin.bat \$\endgroup\$ Aug 31, 2016 at 13:14
  • \$\begingroup\$ Once I fixed my controller, this bot started returning a bunch of bad data, so I removed it from the competition. I can help you in the chat room if you want :) \$\endgroup\$ Sep 1, 2016 at 18:37
1
\$\begingroup\$

UncleScrooge, Java

import java.util.List;
import com.ppcg.stockexchange.*;

public class UncleScrooge extends Player {
    public Offer acceptOffer(List<Offer> offers) {
        Offer offer;
        try {
            offer = offers.get(0);
        } catch (Exception ex) {
            return null;
        }
        if (offer.getPayment() < 100)
            return offer;
        else
            return null;
    }
    public Offer makeOffer(List<Stock> currentStock){
        if (this.getRandom().nextDouble() < 0.6)
            return new Offer(currentStock.get(secretStockType).setAmount(1), Integer.MAX_VALUE);
        else
            return null;
    }
    public void secretValue(int stockType, int value) {
        super.secretValue(stockType, value);
    }
    public void acceptedOffers(List<Offer> acceptedOffers) { }
}

Sell stock at a really high price, and only buy if the price is less than 100.

\$\endgroup\$
1
\$\begingroup\$

Profiteer, Java

Profiteer is in it for the money, and he's always counting coins. He makes a conservative estimate of how much money he's got. He'll then buy the secret stock, if it's less than the value, or buy cheap stock. He also remembers how much he's paid for everything, and always makes offers above the stock price. Furthermore, he'll make higher offers if he has less money.

Note: I think I've done this correctly, but if @NathanMerrill wouldn't mind skimming my code for bugs, that'd be great

import com.ppcg.stockexchange.Offer;
import com.ppcg.stockexchange.Player;
import com.ppcg.stockexchange.Stock;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class Profiteer extends Player {
    private List<StockInfo> onMarket;
    private List<StockInfo> stocks;
    private int money;
    private boolean first = true;

    @Override
    public Offer acceptOffer(List<Offer> offers) {
        Offer finalOffer;

        Optional<Offer> offer = offers.stream().filter(o -> o.getOffer().getType() == this.secretStockType && o.getPayment() < this.secretStockValue * o.getOffer().getAmount()).sorted((a, b) -> Integer.compare((this.secretStockValue * a.getOffer().getAmount()) - b.getPayment(), (this.secretStockValue * b.getOffer().getAmount()) - b.getPayment())).findFirst();
        if (offer.isPresent()) {
            finalOffer = offer.get();
        } else {
            finalOffer = offers.stream().sorted((a, b) -> Integer.compare(a.getPayment(), b.getPayment())).findFirst().orElse(null);
        }

        if (finalOffer == null || this.money <= finalOffer.getPayment()) {
            return null;
        } else {
            this.stocks.add(new StockInfo(finalOffer.getOffer(), finalOffer.getPayment()));
            this.refreshMoney();
            return finalOffer;
        }
    }

    @Override
    public Offer makeOffer(List<Stock> stocks) {
        if (this.first) {
            this.init(stocks);
        } else {
            this.refreshMarketList(stocks);
        }

        Optional<StockInfo> least = this.stocks.stream().sorted((a, b) -> Integer.compare(a.getBoughtPrice(), b.getBoughtPrice())).findFirst();
        Optional<StockInfo> secret = this.stocks.stream().filter(stockInfo -> stockInfo.getStock().getType() == this.secretStockType).sorted((a, b) -> Integer.compare(a.getBoughtPrice(), b.getBoughtPrice())).findFirst();

        StockInfo finalOffer;
        int price;
        if (secret.isPresent()) {
            finalOffer = secret.get();
        } else if (least.isPresent()) {
            finalOffer = least.get();
        } else {
            return null;
        }

        this.onMarket.add(finalOffer);
        this.stocks.remove(finalOffer);
        price = this.calculatePrice(finalOffer.boughtPrice);
        return new Offer(new Stock(finalOffer.getStock().getType(), finalOffer.getStock().getAmount()), price);
    }

    private int calculatePrice(int boughtPrice) {
        return (int) (boughtPrice + ((boughtPrice / (double) this.money) * this.money)) + 1;
    }

    private void refreshMarketList(List<Stock> stocks) {
        this.stocks.addAll(this.onMarket.stream().filter(stockInfo -> stocks.contains(stockInfo.getStock())).collect(Collectors.toList()));
        this.onMarket.clear();
    }

    private void refreshMoney() {
        this.money = this.stocks.stream().mapToInt(info -> this.secretStockType == info.getStock().getType() ? this.secretStockValue : 5).reduce((a, b) -> a + b).orElseGet(() -> 0) - this.stocks.stream().mapToInt(StockInfo::getBoughtPrice).reduce((a, b) -> a + b).orElseGet(() -> 0);
    }

    private void init(List<Stock> stocks) {
        this.stocks = stocks.stream().map(stock -> new StockInfo(stock, 0)).collect(Collectors.toList());
        this.onMarket = new ArrayList<>();
        this.money = 0;
        this.first = false;
        this.refreshMoney();
    }

    private static class StockInfo {
        private Stock stock;
        private int boughtPrice;

        public StockInfo(Stock stock, int boughtPrice) {
            this.stock = stock;
            this.boughtPrice = boughtPrice;
        }

        public Stock getStock() {
            return this.stock;
        }

        public int getBoughtPrice() {
            return this.boughtPrice;
        }

    }

}
\$\endgroup\$
4
  • \$\begingroup\$ you can go into debt and still by stocks... \$\endgroup\$ Aug 30, 2016 at 21:28
  • \$\begingroup\$ @AgentCrazyPython I know, but profiteer doesn't want to risk it \$\endgroup\$ Aug 30, 2016 at 21:32
  • \$\begingroup\$ can't undownvote \$\endgroup\$ Aug 30, 2016 at 23:36
  • \$\begingroup\$ @AgentCrazyPython meh, it's okay, the game is fun and that's what matters \$\endgroup\$ Aug 30, 2016 at 23:48
1
\$\begingroup\$

MaxBot, Java

This bot tries to make the most profit out of each transaction. While selling, puts the price of an unknown stock at $300, when buying $250.

import java.util.List;
import com.ppcg.stockexchange.*;
public class MaxBot extends Player {
    int toSell;
    int sellPrice;
    
    public void secretValue(int stockType, int value) {
        super.secretValue(stockType, value);
        toSell = stockType;
        sellPrice = (value + 1000)/2;
    }
    public Offer acceptOffer(List<Offer> offers) {
        Offer max = null;
        int maxDif = 0;
        for(Offer o: offers){
            int price = secretStockType == o.getOffer().getType()? secretStockValue: 250;
            int val = price * o.getOffer().getAmount();
            int dif = val - o.getPayment();
            if(maxDif < dif){
                max = o;
                maxDif = dif;
            }
        }
        return max;
    }
    public Offer makeOffer(List<Stock> currentStock){
        if(toSell == -1){
            return null;
        }
        int sum = 0;
        for (Stock s: currentStock){
            if(s.getType() == toSell){
                sum += s.getAmount;
            }
        }
        int n = sum - sum/2;
        return new Offer(new Stock(toSell, n), n * sellPrice);
    }
    public void acceptedOffers(List<Offer> acceptedOffers) {
        int highStock = -1;
        int highPrice = 0;
        int markup = 0;
        for(Offer o: offers){
            int trueVal = secretStockType == o.getOffer().getType()? secretStockValue: 250;
            int marketVal = o.getPayment()/o.getOffer().getAmount();
            if(marketVal - trueVal > markup){
                highStock = o.getOffer().getType();
                highPrice = marketVal;
                markup = marketVal - trueVal;
            }
        }
        toSell = highStock;
    }
}
\$\endgroup\$
0
\$\begingroup\$

BlackMarket, Java

Not much to say about this one, seeing as these transactions will be... off the charts, you might say.

import java.util.List;
import com.ppcg.stockexchange.*;

public class BlackMarket extends Player {
    private boolean approvedBySEC = false;
    private int ammoLeft = 30;
    public String taxView = "We want higher tax rates";
    public String excuse = "I never saw that in my life";

    public void secretValue(int drugType, int warrantForMyArrest) {
        super.secretValue(drugType, warrantForMyArrest);
        if (warrantForMyArrest != 0 || drugType == 420) {
            ammoLeft += 10;
        }
    }

    public Offer acceptOffer(List<Offer> offers) {
        for (Offer offer : offers) {
            if (this.approvedBySEC || offer.getPayment() < 9)
                return offer;
        }
        return null;
    }
    
    
    public Offer makeOffer(List<Stock> currentStock) {
        return new Offer(new Stock(0,1),420);
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ return null is outside... please indent properly too. \$\endgroup\$ Aug 31, 2016 at 20:37
  • 1
    \$\begingroup\$ @AgentCrazyPython Thanks! We'll retrieve the "return null" as soon as it's dark. Be wary of cars following you for the next month. \$\endgroup\$
    – Timtech
    Aug 31, 2016 at 21:01
  • 1
    \$\begingroup\$ Why the downvote? We placed fourth in the last competition. Although we're not exactly sure how... \$\endgroup\$
    – Timtech
    Sep 2, 2016 at 16:16
0
\$\begingroup\$

NotQuiteABanksBestFriend, Python 3

Command.txt:

python3 NotQuiteABanksBestFriend.py
NotQuiteABanksBestFriend.py
import random
from functools import total_ordering
from io import StringIO

log = StringIO()
log.write("\n\n~~~NEW GAME~~~\n\n")

LIST_DELIMITER = ';'
STOCK_DELIMITER = ':'
OFFER_DELIMITER = '@'

JAVA_MAX_INT = 2147483647

@total_ordering
class Stock:
    @staticmethod
    def parse(string: str):
        return Stock(*map(int, string.split(STOCK_DELIMITER)))

    def __init__(self, stock_type: int, amount: int):
        self.type = stock_type
        self.amount = max(amount, 0)

    def __str__(self):
        return "T%sx%s"%(self.type, self.amount)

    def __repr__(self):
        return str(self.type)+STOCK_DELIMITER+str(int(self.amount))

    def __bool__(self):
        return bool(self.amount)

    def __eq__(self, other):
        return self.amount == other.amount

    def __lt__(self, other):
        return self.amount < other.amount

    def update(self, amount) -> 'Stock':
        return Stock(self.type, amount)

    def __mul__(self, other: int) -> 'Stock':
        return self.update(self.amount*other)

    def __floordiv__(self, other: int) -> 'Stock':
        return self.update(self.amount//other)

    def __add__(self, other: int) -> 'Stock':
        return self.update(self.amount+other)

    def __sub__(self, other: int) -> 'Stock':
        return self.update(self.amount-other)


class Offer:
    @staticmethod
    def parse(string: str) -> 'Offer':
        try:
            stock, price = string.split(OFFER_DELIMITER)
        except ValueError:
            raise Exception("Cannot unpack "+string)
        return Offer(Stock.parse(stock), int(price.strip()))

    def __init__(self, stock: Stock, price: int):
        self.stock = stock
        self.price = price
        try:
            self.price_per_unit = self.price/self.stock.amount
        except ZeroDivisionError:
            self.price_per_unit = float('inf')

    def __str__(self):
        return "%s$%s"%(self.stock, self.price)

    def __repr__(self):
        return repr(self.stock)+OFFER_DELIMITER+str(int(self.price))


def read_stock_value(value: str):
    global hidden_price, hidden_stock
    stock, price = value.split(STOCK_DELIMITER)
    hidden_price = float(price)
    hidden_stock = int(stock)
    log.write("Hidden StockID: %s\nHidden Price: %s\n"%(hidden_stock, hidden_price))

def process_input():
    handlers = {
        "SecretValue": read_stock_value,
        "RandomSeed": read_seed,
        "MakeOffer": make_offer,
        "AcceptOffer": accept_offer,
        "AcceptedOffers": accepted_offers,
    }
    method = input().strip()
    data = input().strip()
    output = handlers[method](data)
    if output is not None:
        print(repr(output))
    else:
        print()

def read_seed(seed: str):
    random.seed(int(seed))

def start():
    while True:
        process_input()

hidden_stock = None
hidden_price = None

def filter_offers(offer):
    if offer.stock.amount == 0:
        return False
    if offer.price_per_unit > 1000:
        return False
    return True

def certain_profit(offer):
    stock = offer.stock
    if stock.type == hidden_stock and offer.price_per_unit < hidden_price:
        log.write("Offer, %s is certainly profitable.\n"%offer)
        return True
    return False

def make_offer(current_stock: str):
    current_stock = list(map(Stock.parse, current_stock.split(LIST_DELIMITER)))
    own_stock = [stock for stock in current_stock if stock.type == hidden_stock]
    if own_stock and own_stock[0]:
        own_stock = own_stock[0]
        amount_sold = min(random.randrange(1,50), own_stock.amount)
        price = hidden_price+random.randrange(10,50)
        return Offer(Stock(hidden_stock, amount_sold), price*amount_sold)
    sell_stock = random.choice(current_stock)
    amount_sold = min(random.randrange(1,50), sell_stock.amount)
    price = random.randrange(1000, JAVA_MAX_INT//(amount_sold or 1))
    return Offer(Stock(sell_stock.type, amount_sold), price*(amount_sold or 1))

def accept_offer(available_offers: str):
    available_offers = list(map(Offer.parse, available_offers.split(LIST_DELIMITER)))
    filtered_offers = list(filter(filter_offers, available_offers))
    profitable = list(filter(certain_profit, filtered_offers))
    rtn_list = filtered_offers
    if profitable:
        log.write("Profitable: %s\n"%profitable)
        rtn_list = profitable
    if not rtn_list:
        return None
    accepted_offer = min(rtn_list, key=lambda offer: offer.price_per_unit)
    log.write("Bidded for %s\n"%accepted_offer)
    return accepted_offer

def accepted_offers(offers: str):
    pass


if __name__ == "__main__":
    try:
        start()
    finally:
        log.close()

Always tries to sell hidden stock for more than it's worth.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.