JVLinkで競馬データJVDataを取得する擬似コードと説明

VBAでJVLinkを使ってJRAが提供する競馬データJVDataのレコードを取得するのはとても単純です。レコード取得の一連の流れを擬似コードで書くと次のようになります。擬似コードなのでVBAに限らずどの言語でも当てはまります。

ソフトウェアID = "JRA-VANに登録した際に指定されるあなたのソフトウェアを区別する文字列、未登録の際はUNKNOWNとする決まり"
ret = JVLink.JVInit(ソフトウェアID)
if ret < 0 then error

データ種別ID = "レース情報はRACEなど仕様書で定義されている4文字の文字列"
データの開始日時 = "yyyymmddhhnnss"
オプション = 1, 2, 3, 4のいずれかの整数
ret = JVLink.JVOpen(データ種別ID, データの開始日時, オプション, 読み込むjvdファイル数, ダウンロードするjvdファイル数, 最新のjvdファイルのタイムスタンプ)
if ret < 0 then error

do
  ret = JVLink.JVStatus()
  if ret = ダウンロードするjvdファイル数 then exit loop
  elseif ret >= 0 then jvdファイルダウンロード中
  elseif ret < 0 then error
loop

レコード長 = 102890
for jvdFileIndex = 1 to 読み込むjvdファイル数
  do
    ret = JVLink.JVGets(レコードバッファ, レコード長, jvdファイル名)
    if ret > 0 then レコード処理プロセス
    elseif ret = 0 then jvdファイルの切り替え点なので何もしない
    elseif ret = -1 then exit loop
    elseif ret <= -2 then error
  loop
next

ret = JVLink.JVClose()
if ret < 0 then error

処理は大きく5つに分かれています[1]

  1. JVInit()でJVLinkを初期化する。
  2. JVOpen()でJVLinkとの接続をオープンする。
  3. JVStatus()でjvdファイルダウンロード完了を待つ。
  4. JVGets()で1つずつレコードを取得する。
  5. JVClose()で接続をクローズする。

JVLinkを使用するには最初にJVInit()でJVLinkが使用する変数を初期化します。JVInitの引数はソフトウェアIDだけです。

次に、JVOpen()でJVDataサーバーとの接続をオープンします。JVLinkはインターネット経由でjvdファイルというレコードの集まりをサーバーからダウンロードして、暗号をデコードします。そのためのチャネルをオープンするプロセスです。ユーザーが指定するJVOpen()の引数は、データ種別IDデータの開始日時オプションの3つです。読み込むjvdファイル数ダウンロードするjvdファイル数最新のjvdファイルのタイムスタンプの3つの引数はJVOpen()が成功した場合、JVLinkによって値が代入されます。

例えば、データ種別ID = "TOKURACE"データの開始日時 = "20170101000000"オプション = 1と指定してJVOpenをコールすると、JVOpenの戻り値が0(オープン成功)の場合、読み込むjvdファイル数 = 300ダウンロードするjvdファイル数 = 100最新のjvdファイルのタイムスタンプ = "20170813170000"という感じです。

データ種別IDは、TOKUなら特別登録馬情報、RACEならレース詳細情報など全部で13種類あります[2]

データの開始日時は取得したいレースの開催日時以降という意味です。yyyymmddhhnnss形式の文字列で指定します。データの終了日時は指定しません。自動的に現在の最新レースの開催日時(最新のjvdファイルのタイムスタンプの値)となります。

オプションはデータの時系列的なカテゴリーを表す整数で、1は過去1年分のデータ、2は過去1週間分のデータと次開催レース情報、3は過去全てのデータ(コールのたびにオンラインかDVDかのデータソース選択画面が現れる)、4は過去全てのデータ(データソース選択画面が最初のコールのみ現れる)です。

読み込むjvdファイル数は、上記3つの引数で指定した条件に合うデータを含むjvdファイルの個数です。ファイルの拡張子がjvdなのでjvdファイルと呼んでいます。圧縮されたバイナリデータで1つ数十MBほどです。1つのjvdファイルに数100から数1000のレコードが入っています。JVLinkがjvdファイルのダウンロード、解凍などを管理しています。

ダウンロードするjvdファイル数は、JVLinkがこれからJVDataサーバーからダウンロードするjvdファイルの個数です。デフォルトではダウンロードしたjvdファイルはキャッシュされます。

ダウンロードするjvdファイル数 = 読み込むjvdファイル数 - キャッシュしてあるjvdファイル数

の関係が成り立ちます。読み込むjvdファイルが全てキャッシュされているならダウンロードするjvdファイル数は0になります。

最新のjvdファイルのタイムスタンプは普通、直近の日曜日から現在までの時刻の間になります。週末のレースの確定データが提供されるのがレース開催日の翌日となるからです。

3. jvdファイルのダウンロード待ち

JVOpen()でJVLinkの接続をオープンしたら、読み込むjvdファイル数の値だけjvdファイルがダウンロードされるのを待ちます。ダウンロードが完了したjvdファイルの個数はJVStatus()が教えてくれるので、JVStatus()の戻り値が読み込むjvdファイル数と一致したら全jvdファイルのダウンロードが終了したことになるのでループを抜けます。

4. JVDataレコードの取得

jvdファイルのダウンロードが完了したら1レコードずつJVGets()で取得します。JVGets()の引数は、レコードバッファレコード長jvdファイル名の3つで、ユーザーが指定するのはレコード長だけです。

レコードバッファはByte型の動的配列変数を宣言します。VBAだとDim Record() as Byteです。JVGets()をコールする度にJVLink内部でレコードカウンタがインクリメントされて、1つずつレコードのポインタがレコードバッファに代入されます。レコードは1 Byteずつの文字コードShift-JISのバイナリデータの集まりです。JVLinkはレコード長を最大に、自動でレコードバッファの要素数を変えます。

レコード長はレコードバッファの要素数です。レコード長はレコード種別IDで区別されるレコードの種類によって異なります。レコード種別IDはRAはレース詳細、O1は単勝オッズなど2文字で表される文字列で全部で37種類あります。JVData仕様書によると、最大のレコード長は3連単オッズ(レコード種別ID O6)で102890 Bytesです。したがって、レコード長に102890以上の値を指定しておけば全てのレコード種別IDを余すことなく取得できます。(なんでわざわざレコード長を指定するのか意図がわかりません…)

JVGets()をコールする度に1つずつレコードがレコードバッファに格納されます。レコード取得に成功するとJVGets()の戻り値は取得したレコードのレコード長の値です(引数レコード長はJVGets()で取得するレコードのレコード長の最大値です)。JVGets()の戻り値が0はjvdファイルの切り替え点、-1は全レコードの取得終了を意味します。

例えば、レコード種別IDがRAのレコードを取得するとレコードバッファはこんな感じです。VBAのIDEのローカルウィンドウをキャプチャしました。

レコード例

最初に示した擬似コードはJVGets()の戻り値が-1となったらループを抜けています。1レコードずつパースなどの処理をするならこの処理が自然だと思います。私は実際のプログラムではレコードをjvdファイル単位で蓄積してから、パースなどの処理をまとめて行っているので、JVGets()の戻り値が0(jvdファイルの切り替え点)で一旦ループを抜けて、レコード処理を終えたら、再度ループでJVGets()をコールしています。JVLinkはオープン中(JVOpen()からJVClose()まで)はレコードのインデックスを記憶しているので、このような使い方ができます。

jvdファイル名の値は、レコードバッファに代入したレコードが含まれているjvdファイルの名前です。レコードがどのjvdファイルに入っていたかがわかります。例えば、H6VM2007019920150831170446.jvdというように、2文字のレコード種別IDから始まり拡張子jvdで終わる30文字の文字列です[3]

話がそれるのですが、JVGets()と同じ機能のJVRead()というメソッドがあります。JVGets()で取得するレコードのデータ構造はShift-JISのバイナリデータの配列なのに対し、JVRead()はJVLink内部でShift-JISをデコードしてString型変数にレコードを代入します。

JVRead()の方がJVGets()より簡単に扱えるのですが、レコードフォーマットがずれるという致命的な欠点があります。レース名などの日本語と英数字が混在するとデコードしたレコードはレコード長 = 文字数ではなくなります。

JVData仕様書にレコードが含む項目(レース開催日など)と、項目のデータがある位置(配列のインデックス)と長さ(要素数)の一覧がレコード種別IDごとに定義してあり、これをレコードフォーマットというのですが、JVRead()で取得したレコードはすでにデコードされているため、レコードフォーマットに従ってパースしようとしても、項目の位置と長さが違います。レコードフォーマットは1 Byte単位でデータを特定しているけれど、デコードにして文字列にすると文字単位でデータを扱うからです。というわけで、レコードの取得はJVGets()を使っています。

レコードの取得と処理が終了したら、JVClose()でJVLinkの接続をクローズします。クローズ中にJVClose()をコールしてもエラーも何も起こりません。JVClose()の戻り値をチェックする必要もないと思うのですが、一応0以外の値が戻ったらエラーとするようにしています。

JVLinkによるJVData取得の概要は以上です。


  1. 競馬ソフト開発 ソフトウェア開発キット提供コーナー - JRA-VAN DataLab.にあるJV-Linkインターフェース(Win版)仕様書(Ver.4.5.1)より ↩︎

  2. 競馬ソフト開発 ソフトウェア開発キット提供コーナー - JRA-VAN DataLab.にあるJV-Data仕様書(Ver.4.5.1)の最後の方に書いてあります。 ↩︎

  3. jvdファイルの命名規則は仕様書にありません。この点、JVLinkの仕様が不明瞭でプログラミングしづらくなっています。jvdファイルのダウンロード、解凍、レコード抽出などはJVLinkが行うので、だいたいのことはJVLinkのクライアントが意識する必要はないのですが、かといってjvdファイル名やタイムスタンプなどを全く考慮せずにしていいかというとそうではなく、クライアントは1つ残らずレコードを取得するためにjvdファイルの名前、タイムスタンプなどの情報を管理しなくてはいけません。 ↩︎

Kosuke Maeda / まえだこうすけ

「機械学習で競馬予想して勝てるのか?」をテーマに活動中! QiitaにはR、VBAなどのTipsを投稿しています。