CI2002Main

本記事では Chiarella & Iori (2002) が行った単一銘柄シミュレーションを作成することを通して,本ソフトウェアの使い方を説明する(ただし,パラメータ設定の方法など若干の違いがある).

本記事で扱う内容:

関連するファイル:

Components of this simulation

本ソフトウェアは市場の要素を表す以下の基本クラスからなる.

人工市場シミュレーションはこれらの基本クラスおよび派生クラスを組み合わせることで実現される. 本記事の目的は,その組み合わせ方,ひとつのシミュレーションを構築する手順を説明することにある.

ただし,以下の用語に留意する.

Compile and run

$ x10c++ samples/CI2002/CI2002Main.x10
$ ./a.out samples/CI2002/config.json

Chiarella & Iori (2002)

Chiarella & Iori (2002) のモデルは

をもつ. したがって,通常のやり方でこの人工市場シミュレーションを行うには,ユーザは上記の機能を実現するマーケットおよびエージェントという部品を実装し,さらに金融市場の流れに沿ってそれらの部品を組み合わせる「メイン(Main)」プログラムを書かなければならない. また,メイン(Main)には,設定ファイルを読み取り,マーケットやエージェントのパラメータを初期化する,各時点での途中経過を出力するといった処理も含まれる. これらをフルスクラッチで実装するのは容易ではない. そこで,本ソフトウェアを使えば,ユーザはモデルの拡張点に当たる部品を開発するだけで基本的な人工市場シミュレーションを実行できる.

本ソフトウェアを使う場合,上記の機能を実現するマーケットおよびエージェントはそれぞれ次のクラスに既に実装されており,ユーザはこれを利用できる.

以下では FCNAgent の実装を概観し,エージェントクラス作成のポイントを押さえる. 意思決定の方法は原著 Chiarella & Iori (2002) や鳥居・中川・和泉 (2015) を参照してほしい. また,本記事で用いる MarketFCNAgent をどのように X10 で実装したかは部分的には MarketFCNAgent で解説されているが,直接コードを確認してほしい. 自分で新しいエージェントクラスを作成するときには,原著論文と X10 プログラムを見比べると参考になるだろう.

Market

Market クラスは以下のメソッドを含む. Market クラスは連続ダブルオークション(ザラバ方式)で注文を処理する(詳しくはこちら).

// plham/Market.x10
public class Market {

    public def handleOrders(orders:List[Order]) {}       // 注文の処理(約定)
    public def handleOrder(order:Order) {}               // 注文の処理(約定)

    public def getTime():Long {}                         // ステップ時間の取得

    public def getPrice(t:Long):Double {}                // ステップ t の市場価格
    public def getFundamentalPrice(t:Long):Double {}     // ステップ t の理論価格

    public def getBestBuyPrice();     // 買い気配値(最新の値のみ)
    public def getBestSellPrice();    // 売り気配値(最新の値のみ)
    public def getMidPrice();         // 仲値(最新の値のみ)
}

エージェントの意思決定を実装するときにはマーケットの情報を取得する必要がある. マーケットの情報を取得するときには上記のうち,handleOrders()handleOrder()除く,他のメソッドを使うことになる. handleOrders() 類は本ソフトウェアから自動的に呼び出されるので,通常,ユーザがこのメソッドを呼び出すことはない.

留意点として,計算実行モデルによってはマーケットごとにステップ時刻が異なる可能性がある. そのため,すべてのマーケットで getTime() が一致しているという前提で意思決定を実装することは望ましくない.

FCNAgent

FCNAgent クラスは次のような構造をもつ. FCNAgent クラスは Chiarella & Iori (2002) 型の意思決定を実装する(詳しくはこちら).

// plham/agent/FCNAgent.x10
public class FCNAgent extends Agent {

    public var fundamentalWeight:Double;    // 理論価格分析
    public var chartWeight:Double;          // 時系列分析
    public var noiseWeight:Double;          // ノイズ
    public var isChartFollowing:Boolean;
    public var fundamentalMeanReversion:Double;
    public var timeWindowSize:Long;
    public var noiseScale:Double;
    public var orderMargin:Double;

    public def submitOrders(market:Market):List[Order] {}    // 注文の意思決定
}

各フィールドのうち

はそれぞれ取引戦略に対する荷重を表す. これらの頭文字をとって FCN エージェントと呼ぶ. エージェントの注文の決定は以下のメソッドに実装する.

submitOrders(List[Market])Market のリストを受けとり,Order のリストを返す. FCN エージェントは単一銘柄しか取引しないので,FCNAgent.x10 では次のように定義されている.

// plham/Agent.x10
public def submitOrders(markets:List[Market]):List[Order] {
    val orders = new ArrayList[Order]();
    for (market in markets) {
        orders.addAll(this.submitOrders(market));
    }
    return orders;
}

public def submitOrders(market:Market):List[Order] {
    /* Chiarella & Iori (2002) */
}

CI2002Main

メインクラスの役割は JSON ファイルに基づき,要求されるモデルを構築し,要求されるシミュレーションを実行することにある.

ユーザは plham.Main を継承して,新しいメインクラスを作成できる. Main は JSON ファイルにもとづき,シミュレーションに必要な部品を構築する際の基本的な処理や,標準的な人工市場シミュレーションを実行する機能をもつ. そのため,ユーザは自ら拡張定義したエージェントやマーケットを登録するだけでよい. 以下ではまず,このやり方を解説する. 次いで,Main で定義されたシミュレーションの流れを JSON ファイルから制御する方法を解説する.

まず,CI2002Main は次のような構造をもつ.

// samples/CI2002/CI2002Main.x10
public class CI2002Main extends Main {

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

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

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

主要なメソッドは createMarkets(JSON.Value)createAgents(JSON.Value) であり,いずれもスーパークラス Main で定義されており,JSON ファイルをもとにシミュレーションモデルを構築する過程で呼び出される. ユーザに課された責任は createMarkets()createAgents() をオーバーライドすることで,JSON と連携し,エージェントやマーケットをインスタンス化することである.

createMarkets()

まずは createMarkets() を見てみよう. Chiarella & Iori (2002) のマーケットモデルは Market に実装済みなのでこれを利用している.

// samples/CI2002/CI2002Main.x10
public def createMarkets(json:JSON.Value):List[Market] {
    val random = new JSONRandom(getRandom());
    val markets = new ArrayList[Market]();
    if (json("class").equals("Market")) {
        val market = new Market();
        setupMarket(market, json, random);
        markets.add(market);
    }
    return markets;
}

具体的に,Market の初期設定は setupMarket() メソッドを呼び出している.

// samples/CI2002/CI2002Main.x10
public def setupMarket(market:Market, json:JSON.Value, random:JSONRandom) {
    market.setTickSize(random.nextRandom(json("tickSize", "-1.0"))); // " tick-size <= 0.0 means no tick size.
    market.setInitialMarketPrice(random.nextRandom(json("marketPrice")));
    market.setInitialFundamentalPrice(random.nextRandom(json("marketPrice")));
    market.setOutstandingShares(random.nextRandom(json("outstandingShares")) as Long);
}

各所でみられる json(key) は JSON ファイルで定義された key-value ペアを取りだす操作である. 上記のコードでは,Market の属性 の初期値を JSON から読み込み,設定している. JSON 上で乱数分布を指定する記法を可能にするため,JSONRandom#nextRandom() を経由して初期値を設定している(詳しくは こちら).

createMarkets() の引数で与えられる json:JSON.Value は key "Market" に対応した value,すなわち { "class": "Market",... } を格納した JSON.Value オブジェクトである. 以下に示すのはマーケットのプロパティに関する JSON ファイルの一部である. X10 コード(上記 setupMarket() メソッド)との対応関係が読みとれるだろう.

// samples/CI2002/config.json
"Market": {
    "class": "Market",
    "tickSize": 0.00001,
    "marketPrice": 300.0,
    "outstandingShares": 25000
},

Main は JSON ファイルに関していくつかの制約を課しているが,それ以外ではユーザは自由に JSON の key 名を定めてよい. JSON の詳細はこちらを参照してほしい.

createAgents()

createAgents() についても同じ仕方で書かれている. Market 同じく,FCNAgent の属性 の初期値を JSON から読み込み,設定している. ただし,複数のエージェントを生成している点に留意する.

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

具体的な初期値の設定は次の setupFCNAgent() で行われる.

// samples/CI2002/CI2002Main.x10
public def setupFCNAgent(agent:FCNAgent, json:JSON.Value, random:JSONRandom) {
    val MARGIN_TYPES = JSON.parse("{'fixed': " + FCNAgent.MARGIN_FIXED + ", 'normal': " + FCNAgent.MARGIN_NORMAL + "}");

    agent.fundamentalWeight = random.nextRandom(json("fundamentalWeight"));
    agent.chartWeight = random.nextRandom(json("chartWeight"));
    agent.noiseWeight = random.nextRandom(json("noiseWeight"));
    agent.isChartFollowing = (random.nextDouble() < 1.0); // 100%

    agent.noiseScale = random.nextRandom(json("noiseScale"));
    agent.timeWindowSize = random.nextRandom(json("timeWindowSize")) as Long;
    agent.orderMargin = random.nextRandom(json("orderMargin"));
    agent.marginType = MARGIN_TYPES(json("marginType", "fixed")).toLong();

	assert json("markets").size() == 1 : "FCNAgents suppose only one Market";
	val market = getMarketByName(json("markets")(0));
    agent.setMarketAccessible(market);
    agent.setAssetVolume(market, random.nextRandom(json("assetVolume")) as Long);
    agent.setCashAmount(random.nextRandom(json("cashAmount")));
}

以下に示すのは FCN エージェントのプロパティに関する JSON ファイルの一部である.

// samples/CI2002/config.json
"FCNAgents": {
    "class": "FCNAgent",
    "numAgents": 100,

    "MEMO": "Agent class",
    "markets": ["Market"],
    "assetVolume": 50,
    "cashAmount": 10000,

    "MEMO": "FCNAgent class",
    "fundamentalWeight": {"expon": [1.0]},
    "chartWeight": {"expon": [0.0]},
    "noiseWeight": {"expon": [1.0]},
    "noiseScale": 0.001,
    "timeWindowSize": [100, 200],
    "orderMargin": [0.0, 0.1]
}

上記 JSON ファイルの説明はこちらを参照してほしいが,たとえば,"chartWeight" をより大きな数値に設定すれば,チャート重視型戦略の多い場合の取引をシミュレーションできる.

JSON configuration file

ここまで,ユーザが独自のエージェントやマーケットを作成したとき,X10 プログラムをどのように書き,JSON ファイルと連携すればよいかを示した.

最後に,Main で実装済みの人工市場シミュレーションの流れを JSON ファイルから制御する方法を解説する. 以下にその部分の JSON ファイルを示す.

// samples/CI2002/config.json
"simulation": {
    "markets": ["Market"],
    "agents": ["FCNAgents"],
    "sessions": [
        {    "sessionName": 0,
             "iterationSteps": 100,
             "withOrderPlacement": true,
             "withOrderExecution": false,
             "withPrint": true
        },
        {    "sessionName": 1,
             "iterationSteps": 500,
             "withOrderPlacement": true,
             "withOrderExecution": true,
             "withPrint": true
        }
    ]
},

詳しくはこちら を参照してほしい.

Changing Console output

シミュレーションの出力データは Mainprint(...) 関数に記述されている. ユーザはこの print(...) 関数を書き換えることで出力内容を変更できる. 以下に標準の print(...) 関数を示す.

// samples/CI2002/CI2002Main.x10
public def print(sessionName:String) {
	val markets = getMarketsByName("markets");
	val agents = getAgentsByName("agents");
    for (market in markets) {
        val t = market.getTime();
        Console.OUT.println(StringUtil.formatArray([
            sessionName,
            t, 
            market.id,
            market.name,
            market.getPrice(t),
            market.getFundamentalPrice(t),
            "", ""], " ", "", Int.MAX_VALUE));
    }
}

出力データのうち,第 5 列目が市場価格,第 6 列目がファンダメンタル価格である.

Simulations

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