# Copyright 2003 Chris Hibbert under the terms of the MIT license # found at http://www.opensource.org/licenses/mit-license.html # Chris Hibbert can be reached at copyright@mydruthers.com def makeBox := def Math := // upcoming: // def EList := def EList := .asType() def round(n :float64, digits :int) :Twine { def str := "" + n def point :int := str.lastIndexOf(".") def scientific :int := str.lastIndexOf("E") if (scientific > 0) { str } else { if (str.size() < point + digits) { str(0, str.size()) } else { str(0, point + digits) } } } /****************************************************************************** Participants can buy and sell positions in the MarketMaker's markets. Their assets are represented in terms of the asset matrix that the MarketMaker supports. Participants can have assumptions that represent a current sub-region of the market that is of interest for trading and evaluation. ******************************************************************************/ def makeParticipant(theMarket, myAssets) :any { var myAssumptions := theMarket.issues().newAssumptions() def participant { /** buy conditional assets. Scale the MarketMaker's probabilities to reflect that the probability of OUTCOME on ISSUE is VALUE, and scale my payoffs by the same amounts. */ to trade(issue, outcome, value :float64) { theMarket.trade(issue, outcome, value, myAssumptions, myAssets) } ///// Assets ///////////////////////////////////////////////////////////////// to assets() :any { myAssets } /** return my maximum assets under the assumption. */ to maxAssets(assumps) :float64 { theMarket.expToAsset(myAssets.max(assumps)) } /** return my minimum assets under the assumption. */ to minAssets(assumps) :float64 { theMarket.expToAsset(myAssets.min(assumps)) } /** return my average assets (weighted by the MarketMaker's probabilities) under the assumption. */ to aveAssets(assumps) :float64 { theMarket.aveAssets(myAssets, assumps) } /** Print a description of the value of my assets given the possible outcomes of the ISSUE and my current assumptions. */ to showDependentAssetValues(w :TextWriter, issue) { participant.showDependentAssetValues(w, issue, myAssumptions) } /** Print a description of the value of my assets given the possible outcomes of the ISSUE and the ASSUMPTION. */ to showDependentAssetValues(w :TextWriter, issue, assumps) { def index := assumps.validateIssue(issue) if (assumps.issueIsDetermined(issue)) { def theOutcome := assumps.liveOutcomesForIssue(index)[0] def position := issue.positions()[theOutcome] w.print(" According to assumption, the value of <" + issue.desc() + "> is <" + position + ">\n") } else { w.print(`$issue$\n`+ `$\toutcome$\tmax assets$\tave assets$\tmin assets$\n`) for i in assumps.liveOutcomesForIssue(index) { def outcome := issue.positions()[i] def out := assumps.withAssumption(issue, outcome, true) def max := round(participant.maxAssets(out), 5) def ave := round(participant.aveAssets(out), 5) def min := round(participant.minAssets(out), 5) w.print(`$\t$outcome$\t$max$\t$ave$\t$min$\n`) } } } ///// Assumptions //////////////////////////////////////////////////////////// /** Show my Assumptions. */ to assumptions() :any { myAssumptions } /** Change my assumptions. */ to setAssumptions(newAssumption) { myAssumptions := newAssumption } /** Reset my assumptions to be blank. */ to resetAssumptions() { myAssumptions := theMarket.issues().newAssumptions() } /** Drop one of the assumptions I currently have. */ to dropAssumption(issue, outcome) { myAssumptions := myAssumptions.withoutAssumption(issue, outcome) } /** Add an assumption to those I currently have. */ to addAssumption(issue, outcome, value) { myAssumptions := myAssumptions.withAssumption(issue, outcome, value) } to printAssumptions(w :TextWriter) { myAssumptions.printOn(w) } } } /****************************************************************************** The MarketMaker decides what issues to support, and what the value of an investment will be. Participants are able to deposit any multiple of the MarketMaker's initial subsidy. Assets are represented as 2^(value/subsidy). ******************************************************************************/ def makeMarketMaker(issueSet, subsidy :int) :any { def probCube := makeBox.makeProbBox(issueSet.cardinalities()) def marketMaker { to issues() :any { issueSet } /** return the average assets under the assumption weighted by my probabilities). */ to aveAssets(assetBox, assum) :float64 { def p := probCube.sum(assum) require(p >= 0, "internal error: some probabilities are negative") if (p == 0) { 0 } else { def sumOfLogs := assetBox.sumOfThatTimesLogThis(probCube, assum) marketMaker.scale() * (sumOfLogs / p) / Math.log(2) } } /** the scale of the marketMaker's subsidy. */ to scale() :int { subsidy } /** Print a description of the probabilities of the possible outcomes of the issue. */ to showChances(w :TextWriter, issue, assumps) { def index := issueSet.indexOf(issue) if (assumps.issueIsDetermined(issue)) { def theOutcome := assumps.liveOutcomesForIssue(index)[0] w.print(` According to assumption, the value \ of <${issue.desc()}> is <${issue.positions()[theOutcome]}>$\n`) } else { w.print( ` Probabilities for outcomes of <$issue>:$\n$\toutcome$\tprobability$\n`) for i in assumps.liveOutcomesForIssue(index) { def outcome := issue.positions()[i] def prob := round(probCube.probGiven(issue, outcome, assumps), 5) w.print(`$\t${outcome}$\t${prob}$\n`) } } } /** Transform the internal representation (in exponential form) of a Player's holdings to a monetary value. */ to expToAsset(exp :float64) :float64 { subsidy * Math.log(exp) / Math.log(2) } to tradeFactors(issue, outcome, targetProb :float64, assumps) :EList { require(targetProb <= 1 && targetProb >= 0, "new price must be within [0,1].") require(! assumps.outcomeIsDetermined(issue, outcome), "There's already an assumption about that outcome.") def assumeTrue := assumps.withAssumption(issue, outcome, true) def pTrue := probCube.sum(assumeTrue) def assumeFalse := assumps.withAssumption(issue, outcome, false) def pFalse := probCube.sum(assumeFalse) require(0 != pTrue && 0 != pFalse, "Variable's targetProb has already been determined.") def newPTrue := targetProb * (pTrue + pFalse) def trueFactor := newPTrue/pTrue def newPFalse := pFalse + pTrue - newPTrue def falseFactor := newPFalse/pFalse [trueFactor, falseFactor, assumeTrue, assumeFalse] } to quote(issue, outcome, targetProb :float64, assumps) :EList { def [trueFactor, falseFactor, ignoreA, ignoreB] := marketMaker.tradeFactors(issue, outcome, targetProb, assumps) def truePrice := round(- marketMaker.expToAsset(trueFactor), 5) def falsePrice := round(marketMaker.expToAsset(falseFactor), 5) if (trueFactor < 1) { [truePrice, falsePrice] } else { [falsePrice, truePrice] } } to quote(issue, outcome, targetProb :float64, assumps, w :TextWriter) { def [cost, gain] := marketMaker.quote(issue, outcome, targetProb, assumps) w.print(`To set P($outcome) on $issue to $targetProb it will cost $cost if wrong, and gain $gain if right.`) } /** A Player wants to set the probability of Outcome (given Assumptions) on Issue to Value. Change the trader's assets and the market maker's current probabilities to represent that.

Calculate the current relative probability of Outcome (given Assumptions) according to the ratio of the total values in the true and false sub-regions of probBox. Calculate factors to be applied to the true and false sub-regions which will make the ratio of their sums have the target value. Scale the values in each sub-region by the appropriate factor. Then scale the Player's assets in the sub-regions by the same amounts. */ to trade(issue, outcome, targetProb :float64, assumps, assets) { def [trueFactor, falseFactor, assumeTrue, assumeFalse] := marketMaker.tradeFactors(issue, outcome, targetProb, assumps) def sufficientAssets := if (trueFactor < 1) { 1/trueFactor < assets.min(assumeTrue) } else { 1/falseFactor < assets.min(assumeFalse) } require(sufficientAssets, "Not enough assets to pay for trade.") probCube.scale(trueFactor, assumeTrue) probCube.scale(falseFactor, assumeFalse) assets.scale(trueFactor, assumeTrue) assets.scale(falseFactor, assumeFalse) } /** create a new Player in the market. */ to makeParticipant(deposit :int) :any { require(deposit %% subsidy == 0, `deposit must be a multiple of $subsidy.`) def asset := 2 ** (deposit / subsidy) def assets := makeBox.makeAssetBox(issueSet.cardinalities(), asset) makeParticipant(marketMaker, assets) } } }