ShockTransferMain

本記事では,鳥居,中川,和泉 (2015) が行ったショック伝搬のシミュレーションを作成することを通して,本ソフトウェアの使い方を説明する.

本記事で扱う内容:

関連するファイル:

Preface

鳥居,中川,和泉 (2015) のシミュレーションは,2 つの現物銘柄(単に Market)と 1 つの指数銘柄(IndexMarket)から構成され,個別銘柄を独立に取引する FCN エージェントの他に,現物と指数の価格差から利益をえる裁定エージェント(ArbitrageAgent)が取引を行う. 研究目的は,ある現物銘柄で生じた価格の急落が,裁定取引を媒介して,指数を構成する他の銘柄へどのようにショック伝搬するかを調べることにある.

FCNAgent および Market はすでに CI2002Main の記事で説明済みであるので,本稿では解説しない. また,ショックに関しては TradingHaltMain の記事を参照してほしい.

本記事では,指数銘柄(IndexMarket)および裁定エージェント(ArbitrageAgent)をシミュレーションに組み込む方法を説明する.

IndexMarket

以下に IndexMarket の骨格を示す. 指数銘柄に相当する IndexMarket は,現物銘柄に相当する複数の Market と紐付けられる. 株価指数の計算方式には一般に価格平均指数と時価総額平均指数の 2 種類があるが,下記で述べるように,それぞれのクラスが用意されている.

// plham/IndexMarket.x10
public class IndexMarket extends Market {

    public def addMarket(market:Market) {}            // 指数構成銘柄に追加
    public def addMarkets(markets:List[Market]) {}    // 指数構成銘柄に追加
    public def getMarkets():List[Market] {}           // 指数構成銘柄を取得

    public def setMarketIndexMethod(method:MarketIndex) {}         // 指数計算方式
    public def setFundamentalIndexMethod(method:MarketIndex) {}    // 指数計算方式

    public def getMarketIndex(t:Long) {}              // 市場価格指数の取得(時点 t)
    public def getFundamentalIndex(t:Long) {}         // 理論価格指数の取得(時点 t)

    public def computeMarketIndex():Double {}         // 市場価格指数の計算(再計算)
    public def computeFundamentalIndex():Double {}    // 理論価格指数の計算(再計算)
}

株価指数の計算方式には価格平均指数と時価総額平均指数の 2 種類はそれぞれ

に実装されている. 具体的なインスタンス化の手順は ShockTransferMain.x10 を参照してほしい.

ArbitrageAgent

現物-指数間の裁定取引を行うエージェントは ArbitrageAgent クラスに実装されている.

// plham/agent/ArbitrageAgent.x10
public class ArbitrageAgent extends HighFrequencyAgent {

    public def submitOrders(market:Market):List[Order] {
        val index = market as IndexMarket;
        val spots = index.getMarkets();
        /* ... */
    }
}

FCNAgent との違いは (a) HighFrequencyAgent を継承している点,(b) IndexMarket のみを取引対象とする点にある. IndexMarket#getMarkets() メソッドを介して,現物銘柄のリストへアクセスする.

(a) HighFrequencyAgent はマーカーの役割を果たし,本ソフトウェアがエージェントのタイプ「高速取引」を識別するために用いられる. 具体的には,HighFrequencyAgent を継承したエージェントクラスは,エージェントシミュレーションの流れのうち,高速取引を行える位置に組み込まれる.

(b) 現物銘柄へのアクセスは index.getMarkets() から行う. 意思決定の詳細は後続の節か,plham/agent/ArbitrageAgent.x10 を直接参考にしてほしい.

ShockTransferMain

ShockTransferMainCI2002Main を継承し,次のような構造をもつ.

public class ShockTransferMain extends CI2002Main {

	public static def main(args:Rail[String]) {
		new SequentialRunner(new ShockTransferMain()).run(args);
	}

	public def createMarkets(json:JSON.Value):List[Market] {}

	public def createAgents(json:JSON.Value):List[Agent] {}

	public def createEvents(json:JSON.Value):List[Event] {}
}

コードに示されるように,createMarkets()createAgents()createEvents() のすべてにおいて,シミュレーションに必要な新しい部品を追加していく.

createMarkets()

createMarkets() では IndexMarket をインスタンス化する. 通常の Market に関しては CI2002Main での定義を再利用する.

// samples/ShockTransfer/ShockTransferMain.x10
public def createMarkets(json:JSON.Value):List[Market] {
	val random = new JSONRandom(getRandom());
	val markets = super.createMarkets(json); // Use Market defined in CI2002Main.
	if (json("class").equals("IndexMarket")) {
		val market = new IndexMarket();
		setupIndexMarket(market, json, random);
		markets.add(market);
	}
	return markets;
}

IndexMarket をインスタンス化するさい,addMarket() を呼び出し,株価指数構成銘柄の追加を JSON ファイルと連携しながら行う必要がある. これを行うには,ユーザはインスタンス化済みの他のマーケットを取得する必要があるが,これは GLOBAL(String) を介して行える.

具体的には,GLOBAL には,インスタンス化済みのマーケット,エージェント,イベントなどが JSON で宣言したマーケット名を key として GLOBAL(key) に保存されている. インスタンス化は (1) マーケット,(2) エージェント,(3) イベント(金融規制/金融ショック)(順不定)の順番で行われ,先にインスタンス化されたオブジェクトにのみ,GLOBAL を介してアクセスできる. マーケットのインスタンス化は相互依存関係を調べたうえで実行されるため,マーケット名が間違っていたり,循環関係が宣言されていない限り,マーケット keyGLOBAL に不在ということはない. GLOBAL には createMarkets()createAgents()createEvents() の返り値がそのまま登録されているため,これらに関して言えば,key に対応する value すなわち GLOBAL(key) はすべて List[T] 型である.

実装上では,下記の補助メソッド群が提供されており,ユーザが GLOBAL に直接アクセスすることは少ないと思われる.

Market Agent Event
getMarketsByName() getAgentsByName() getEventsByName()
getMarketByName() getAgentByName() getEventByName()

具体的には以下のようにする.

// samples/ShockTransfer/ShockTransferMain.x10
public def setupIndexMarket(market:IndexMarket, json:JSON.Value, random:JSONRandom) {
	market.setTickSize(random.nextRandom(json("tickSize", "-1.0"))); // " tick-size <= 0.0 means no tick size.

	val spots = getMarketsByNames(json("markets"));
	market.addMarkets(spots);

	// WARN: Market's methods access to market.env is not available here :WARN

    // 市場価格指数
	val marketIndex = new CapitalWeightedIndexScheme(CapitalWeightedIndexScheme.MARKET_PRICE);
	marketIndex.setIndexDivisor(random.nextRandom(json("marketPrice")), marketIndex.getIndex(spots));
	market.setMarketIndexScheme(marketIndex);

    // 理論価格指数
	val fundamIndex = new CapitalWeightedIndexScheme(CapitalWeightedIndexScheme.FUNDAMENTAL_PRICE);
	fundamIndex.setIndexDivisor(random.nextRandom(json(["fundamentalPrice", "marketPrice"])), fundamIndex.getIndex(spots));
	market.setFundamentalIndexScheme(fundamIndex);

    // 指数の値を使い IndexMarket を初期設定
	market.setInitialMarketPrice(marketIndex.getIndex(spots));
	market.setInitialMarketIndex(marketIndex.getIndex(spots));
	market.setInitialFundamentalPrice(fundamIndex.getIndex(spots));
	market.setInitialFundamentalIndex(fundamIndex.getIndex(spots));
	market.setOutstandingShares(random.nextRandom(json("outstandingShares")) as Long);
}

createAgents()

createAgents() の中では ArbitrageAgent をインスタンス化する. FCNAgent に関しては CI2002Main での定義を再利用する.

// samples/ShockTransfer/ShockTransferMain.x10
public def createAgents(json:JSON.Value):List[Agent] {
	val random = new JSONRandom(getRandom());
	val agents = super.createAgents(json); // Use FCNAgent defined in CI2002Main.
	if (json("class").equals("ArbitrageAgent")) {
		val numAgents = json("numAgents").toLong();
		for (i in 0..(numAgents - 1)) {
			val agent = new ArbitrageAgent();
			setupArbitrageAgent(agent, json, random);
			agents.add(agent);
		}
	}
	return agents;
}

ArbitrageAgent の初期設定に関して特別な点はないので説明を省く.

// samples/ShockTransfer/ShockTransferMain.x10
public def setupArbitrageAgent(agent:ArbitrageAgent, json:JSON.Value, random:JSONRandom) {
	agent.orderVolume = json("orderVolume").toLong();
	agent.orderThresholdPrice = json("orderThresholdPrice").toDouble();

	assert json("markets").size() == 1 : "ArbitrageAgents suppose only one IndexMarket";
	assert getMarketByName(json("markets")(0)) instanceof IndexMarket : "ArbitrageAgents suppose only one IndexMarket";
	val market = getMarketByName(json("markets")(0)) as IndexMarket;
	agent.setMarketAccessible(market);
	for (id in market.getComponents()) {
		agent.setMarketAccessible(id);
	}

	agent.setAssetVolume(market, random.nextRandom(json("assetVolume")) as Long);
	for (id in market.getComponents()) {
		agent.setAssetVolume(id, random.nextRandom(json("assetVolume")) as Long);
	}
	agent.setCashAmount(random.nextRandom(json("cashAmount")));
}

createEvents()

createEvents() では FundamentalPriceShock をインスタンス化する. 取引停止の事例と同じなのでこちらを参照してほしい.

JSON ファイルによるシミュレーションの制御

CI2002Main からの最大の違いは,"markets" に複数のマーケットを指定していること,また "agents" に複数のエージェントを指定していることである. これらの指定する順番は任意であり,シミュレーションの初期化の順番に影響しない. すなわち,マーケット間の相互依存関係はソフトウェアにより自動的に認識され,依存関係を解消する順番でインスタンス化される.

// samples/ShockTransfer/config.json
"simulation": {
	"markets": ["SpotMarket-1", "SpotMarket-2", "IndexMarket-I"],
	"agents": ["FCNAgents-1", "FCNAgents-2", "FCNAgents-I", "ArbitrageAgents"],
	"sessions": [
		{	"sessionName": 0,
			"iterationSteps": 100,
			"withOrderPlacement": true,
			"withOrderExecution": false,
			"withPrint": true,
			"maxNormalOrders": 3, "MEMO": "The same number as #markets",
			"maxHifreqOrders": 0
		},
		{	"sessionName": 1,
			"iterationSteps": 500,
			"withOrderPlacement": true,
			"withOrderExecution": true,
			"withPrint": true,
			"maxNormalOrders": 3, "MEMO": "The same number as #markets",
			"maxHifreqOrders": 5,
			"events": ["FundamentalPriceShock"]
		}
	]
}

Simulations

次に,このプログラムを使って,シミュレーションを実行してみよう(こちら).