# 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 // IssueSets and Assumptions. ///////////////////////////////////////////////// // upcoming: // def EList := def EList := .asType() /** helper function: count number of elements for which test(elt) is true */ def countMatches(array :EList, test) :int { var count := 0 for j in array { if (test(j)) { count += 1 } } count } /** helper function: count elements that are true. */ def countTrue(array :EList) :int { countMatches(array, def _(j) :any { j == true }) } /** helper function: count non-false elements. */ def countNonFalse(array :EList) :int { countMatches(array, def _(j) :any { j != false }) } /** return an array of nulls the same size as the IssueSet */ def nullAssumptions(issues) :pbc { [null] * issues.cardinalities().size() } /****************************************************************************** Represents assumptions about outcomes pertaining to an issue set. Elements represent positions on each of the Issues. Assumptions objects explicitly represent whether they say anything about each issue and each possible outcome. Any element can be null, indicating no assumptions on that issue.

The elements contain true, false, or null for each position on the issue that they represent. Null indicates no assumption. There can be at most one true position on an issue, and there has to be at least one position that is either true or null. (i.e. It can't be the case that all positions are false.)

Assumptions are used to delineate subregions of the issue multi-dimensional rectangle. They are used both for specifying assumptions participants make in order to make conditional investments, and by the trading apparatus to enumerate the included and excluded regions of a proposed investment. ******************************************************************************/ def makeAssumptions(issues, var assumeList :nullOk(EList)) :any { if (null == assumeList) { assumeList := nullAssumptions(issues) } else { assumeList := assumeList.snapshot() } def assumptions { /** Return a new Assumptions object that doesn't say anything about OUTCOME of ISSUE. Throws if the issue isn't in the IssueSet, the outcome isn't relevant to the issue, or there isn't a current assumption on the issue. */ to withoutAssumption(issue, outcome) :any { def [indexOfIssue, indexOfOutcome] := assumptions.validateIssueOutcome(issue, outcome) def currentIssue := assumeList[indexOfIssue] require(null != currentIssue, "no current assumption") def newIssue := currentIssue.with(indexOfOutcome, null) makeAssumptions(issues, assumeList.with(indexOfIssue, newIssue)) } /** Return a new Assumption that specifies whether OUTCOME of ISSUE will be true or false. Throws if the issue isn't in the IssueSet, the outcome isn't relevant to the issue, there is already an assumption on the issue, or the specified value is incompatible with the existing assumptions. */ to withAssumption(issue, outcome, value :boolean) :any { def [indexOfIssue, indexOfOutcome] := assumptions.validateIssueOutcome(issue, outcome) def currentIssue := assumeList[indexOfIssue] var newIssue := currentIssue if (null == currentIssue) { newIssue := [null] * issue.cardinality() } else { def curOutcome := currentIssue[indexOfOutcome] require(curOutcome != value, "no change to assumptions") if (value) { // setting outcome true; no other can be true now require(countTrue(currentIssue) == 0, "inconsistent assumption: more than one true.") } else { // curOutcome null or true ; need another that may be true require(countNonFalse(currentIssue) > 1, "inconsistent assumption: no true outcome.") } } newIssue := newIssue.with(indexOfOutcome, value) makeAssumptions(issues, assumeList.with(indexOfIssue, newIssue)) } /** return the assumptions */ to values() :EList { assumeList } /** print a representation of the assumptions that shows which outcomes are determined, and which are open. */ to printOn(w :TextWriter) { for i in 0..!issues.cardinalities().size() { def issue := issues.issue(i) def desc := issue.desc() if (assumptions.issueIsDetermined(issue)) { def theOutcome := assumptions.liveOutcomesForIssue(i)[0] def outcomeName := issue.positions()[theOutcome] w.print(`$desc:$\t$outcomeName$\n`) } else if (null == assumptions.values()[i]) { w.print(`$desc:$\tunknown$\n`) } else { w.print(`$desc:$\t${issue.positions()}$\n` + `$\t$\tpossible outcomes:$\t`) for outcome in assumptions.liveOutcomesForIssue(i) { w.print(issue.positions()[outcome] + "\t") } w.print("\n") } } } /** return the indexes of the outcomes that are possible according to the assumptions. */ to liveOutcomesForIssue(indexOfIssue :int) :pbc { def currentIssueAssumptions := assumeList[indexOfIssue] def currentIssue := issues.issue(indexOfIssue) if (null == currentIssueAssumptions) { currentIssue.indexes().getElements() } else if (1 == countTrue(currentIssueAssumptions)) { [currentIssueAssumptions.lastIndexOf1(true)] } else if (1 == countNonFalse(currentIssueAssumptions)) { [currentIssueAssumptions.lastIndexOf1(null)] } else { def liveOutcomes := [].diverge() for i in 0..!currentIssue.cardinality() { if (false != currentIssueAssumptions[i]) { liveOutcomes.push(i) } } liveOutcomes.snapshot() } } /** return the indexes of the ISSUE in the market and of the OUTCOME within the issue as an array. Throws if ISSUE and OUTCOME aren't consistent with the market. */ to validateIssueOutcome(issue, outcome) :EList { def indexOfIssue := assumptions.validateIssue(issue) def indexOfOutcome := issue.indexOf(outcome) require(indexOfOutcome != -1, `not a valid outcome on this issue: $outcome`) [indexOfIssue, indexOfOutcome] } /** return the index of the ISSUE in the market. Throws if ISSUE isn't consistent with the market. */ to validateIssue(issue) :int { def indexOfIssue := issues.indexOf(issue) require(indexOfIssue != -1, `not a valid issue in this market: ${ issue }`) indexOfIssue } /** do the assumptions determine the value of the outcome? */ to outcomeIsDetermined(issue, outcome) :boolean { def [indexOfIssue, indexOfOutcome] := assumptions.validateIssueOutcome(issue, outcome) def curIssue := assumeList[indexOfIssue] if (null != curIssue) { // return (null != curIssue[indexOfOutcome] || 1 == countTrue(curIssue) || 1 == countNonFalse(curIssue)) } else { false } } /** return true if the assumptions determine which outcome on ISSUE is true. */ to issueIsDetermined(issue) :boolean { def indexOfIssue := assumptions.validateIssue(issue) def curIssue := assumeList[indexOfIssue] if (null != curIssue) { // return 1 == countTrue(curIssue) || 1 == countNonFalse(curIssue) } else { false } } } } /****************************************************************************** IssueSet represents the particular set of issues handled by a MarketMaker. It consists of a Set of Issues. There may be more responsibilities later. The issues() method is only present for debugging, and may go away later. *****************************************************************************/ def makeIssueSet(myIssues) :any { def issueSet { to cardinalities() :EList { var result := [null] * myIssues.size() for i => issue in myIssues { result := result.with(i, issue.cardinality()) } result } to indexOf(issue) :int { myIssues.lastIndexOf1(issue) } to newAssumptions() :any { makeAssumptions(issueSet, null) } to issue(index) :any { myIssues[index] } } }