① MARKETSPEED II RSS は公式機能で、データ取得だけでなく発注機能を備えています。利用は無料(MARKETSPEED IIの申込は必要)
② 実体はExcelのアドインです。Pythonからは xlwings / pywin32 でExcelを操作する形になります
③ Windows常駐が前提です。MARKETSPEED IIとExcelを起動したままにする必要があり、Linuxサーバーでは動きません
④ 対象は国内株・先物OP・商品先物。外国株・海外先物・FX・CFDは対象外です
動作環境(対応OS・対応Excelバージョン)には制約があります。公式の告知時点ではOffice 365が対象外とされていました。要件は更新されている可能性があるため、導入前に必ず楽天証券公式の最新の動作環境を確認してください。
MARKETSPEED II RSS(マーケットスピード II RSS)は、楽天証券が提供するExcel連携機能です。RSSは "Realtime Spread Sheet" の略で、Excelのセルに専用関数を書くと、そこにリアルタイムの市場データが流れ込みます。
2021年に発注機能が搭載されたことで、「データを見るツール」から「自動売買を組めるインターフェース」に変わりました。
| 区分 | できること | 補足 |
|---|---|---|
| マーケットデータ | 四本値・現在値・前日比・気配・歩み値・板情報 | リアルタイムでセルが自動更新される |
| 口座情報 | 買付可能額・保有ポジション・注文状況・約定履歴 | 残高照会をExcel上で自動化できる |
| 発注 | 関数を用いた新規注文・訂正・取消 | VBA/マクロと組み合わせて自動売買が可能 |
| 対象商品 | 国内株式(現物・信用)、株価指数先物・オプション、商品先物 | 外国株・海外先物・FX・CFDは対象外 |
| 費用 | 無料 | MARKETSPEED II 自体の利用申込が必要 |
RSSはWindows専用で、特定バージョンのMicrosoft Excelを必要とします。公式告知の時点では対応Officeが限定列挙され、Office 365は対象外と明記されていました。
この要件は時期により更新されている可能性が高いため、Excelを買う前・環境を組む前に、必ず楽天証券公式サイトの最新の動作環境ページを確認してください。ここを外すと、環境構築の手戻りが発生します。
「PythonからHTTPで叩ければ楽なのに」と思うところですが、RSSはExcelアドインとして実装されているため、Excelという実行環境の中でしか関数が評価されません。したがってPythonから使う場合、構成はこうなります。
┌──────────────────────────────┐
│ 楽天証券サーバー │
└──────────────┬───────────────┘
│ 認証済みセッション
▼
┌──────────────────────────────┐
│ MARKETSPEED II(常駐・ログイン済み) │
│ └ RSS 機能を「接続中/発注可」にしておく │
└──────────────┬───────────────┘
│ Excelアドイン
▼
┌──────────────────────────────┐
│ Excel ブック(bot.xlsx) │
│ ├ シート"data" : RSS関数で株価が流れ込む │
│ └ シート"order" : 発注関数の引数を書き込む │
└──────────────┬───────────────┘
│ xlwings(COM経由)
▼
┌──────────────────────────────┐
│ Python(戦略ロジック・ログ・通知) │
└──────────────────────────────┘
PythonはExcelのセルを読み書きするだけで、市場との通信はすべてMARKETSPEED IIが担います。Pythonの役割は「セルから数値を読み、戦略を判断し、発注用セルに値を書く」ことに集約されます。
・Linuxサーバーでは動きません。Windows VPSかWindows実機が必要です
・Excelがクラッシュすると全部止まります。Excelは長時間稼働の安定性が保証された実行基盤ではありません
・ヘッドレス実行ができません。Excelのプロセスが生きている必要があります
・PCの自動更新・再起動が事故要因になります。Windows Updateの自動再起動は必ず無効化・時間指定してください
「ポジションを持ったまま環境が落ちる」ことを前提に設計する必要があります。後述の冪等性設計が重要になるのはこのためです。
楽天証券の会員ページから申し込みます。RSSはMARKETSPEED IIの一機能なので、まず本体を入れる必要があります。
ここが最大の関門です。公式の動作環境ページで対応Officeバージョンを確認してください。非対応のExcelではアドインが読み込まれません。
ツール内のRSSメニューから機能を有効にします。ステータスが「接続中」になっていることを確認してください。
発注を使う場合は、MARKETSPEED II 側で注文利用設定を行い、ステータスを「発注可」にします。あわせて発注金額の上限も設定できます。この上限は必ず設定してください。バグによる暴走時の最後の防波堤になります。
適当なセルに株価取得の関数を書き、値が入ることを確認します。ここで #NAME? が出る場合はアドインが読み込まれていません。
Excelと同じPC上にPythonを入れます。32bit/64bitはExcelと揃えておくと事故が減ります。
xlwings>=0.31 # Excel操作(COM経由)
pandas>=2.2
numpy>=1.26
requests>=2.32 # Discord通知用
python-dotenv>=1.0
RSS関数は大きく2系統に分かれます。関数名と引数の正確な仕様は公式マニュアルが唯一の正解なので、ここでは「どういう構造になっているか」を押さえます。
| 系統 | 役割 | Pythonから見たときの扱い |
|---|---|---|
| マーケットデータ系 | 銘柄コードと項目名を渡すと、現在値・気配・出来高などが返る | 読み取り専用。セルを読むだけ |
| 口座情報系 | 買付可能額・保有建玉・注文一覧・約定履歴を返す | 読み取り専用。状態確認に使う |
| 発注系 | 発注IDや銘柄・数量・価格・執行条件を引数に取り、注文を送信する | 書き込み。ここが事故ポイント |
RSSの発注関数は発注IDを引数に取り、同じIDでの重複発注を受け付けない設計になっています。これは極めて重要な安全機構です。
Excelは再計算のたびに関数が評価されるため、この仕組みがなければ「セルが再計算されるたびに発注が飛ぶ」という悪夢が起こります。発注IDによって「1つのIDにつき1回だけ」が保証されるわけです。
したがって、あなたの実装で最も重要なのは「発注IDを一意に、かつ再現可能に採番すること」です。ここを適当にすると、二重発注か、逆に発注されないかのどちらかが起きます。
発注が成立するには、おおむね次がすべて満たされている必要があります。
① RSSが「接続中」 ② ステータスが「発注可」 ③ MARKETSPEED II側の注文利用設定が済んでいる ④ 注文金額が設定した上限内 ⑤ 発注IDが未使用 ⑥ 引数の内容が有効
1つでも欠けると発注されません。そして厄介なことに、②は後述するように自分の操作以外の理由で落ちることがあります。
xlwingsは、起動中のExcelにCOM経由で接続してセルを読み書きするライブラリです。新しくExcelを開くのではなく、すでに開いているブックに接続するのがポイントです。
# 本コードは本人利用に限り自由に使用可。再配布・商用利用・販売は禁止。
import xlwings as xw
# すでに開いている bot.xlsx に接続する(新規に開かない)
book = xw.Book("bot.xlsx")
data = book.sheets["data"]
order = book.sheets["order"]
# --- 読み取り ---
price = data.range("B2").value # 単一セル
table = data.range("A2:F50").value # 範囲(リストのリストで返る)
# pandas DataFrame として読む(ヘッダ付きの表を想定)
import pandas as pd
df = data.range("A1").expand().options(pd.DataFrame, header=1, index=False).value
# --- 書き込み ---
order.range("B2").value = 7203 # 銘柄コード
order.range("B3").value = 100 # 数量
# --- 再計算を明示的に走らせる ---
book.app.calculate()
print(f"現在値: {price}")
RSS関数は非同期にデータを取得するため、セルに書き込んだ直後に読んでも 0 や空、あるいは取得中を示す値が返ることがあります。
「書いたらすぐ読む」コードは必ず失敗します。値が確定するまでポーリングして待つヘルパーが必須です。次のコードに含めています。
ここまでの制約をすべて織り込んだ、実運用を想定した実装です。安全機構を優先した設計にしています。
# 本コードは本人利用に限り自由に使用可。再配布・商用利用・販売は禁止。
# MARKETSPEED II RSS を xlwings 経由で扱うためのラッパー。
# 注意:関数名・引数・セル配置は各自の環境と公式マニュアルに合わせて調整すること。
import time
import logging
from typing import Optional, Any
import xlwings as xw
log = logging.getLogger(__name__)
class RssError(RuntimeError):
"""RSSの状態異常(未接続・発注不可など)"""
class RssClient:
def __init__(self, book_name: str = "bot.xlsx"):
self.book = xw.Book(book_name)
self.data = self.book.sheets["data"]
self.order = self.book.sheets["order"]
self.state = self.book.sheets["state"]
# ---------- 状態確認 ----------
def is_connected(self) -> bool:
"""RSSが接続中かどうか(接続ステータスを表示するセルを参照)"""
return str(self.state.range("B1").value or "").strip() == "接続中"
def is_orderable(self) -> bool:
"""発注可かどうか。別ブックを開くなどで落ちることがあるため毎回確認する"""
return str(self.state.range("B2").value or "").strip() == "発注可"
def assert_ready(self) -> None:
if not self.is_connected():
raise RssError("RSSが接続中ではありません。MARKETSPEED IIを確認してください。")
if not self.is_orderable():
raise RssError(
"ステータスが『発注可』ではありません。"
"別のExcelブックを開いた等で解除された可能性があります。"
"自動で再有効化はせず、手動で状態を確認してください。"
)
# ---------- データ取得 ----------
def wait_value(self, sheet, addr: str, timeout: float = 10.0,
interval: float = 0.3) -> Optional[Any]:
"""RSSは非同期取得のため、値が確定するまで待つ"""
deadline = time.time() + timeout
while time.time() < deadline:
v = sheet.range(addr).value
if v not in (None, "", 0):
return v
self.book.app.calculate()
time.sleep(interval)
log.warning("値の取得がタイムアウトしました: %s", addr)
return None
def get_price(self, code: int) -> Optional[float]:
"""銘柄コードをセットして現在値を読む"""
self.data.range("B1").value = code
self.book.app.calculate()
v = self.wait_value(self.data, "B2")
return float(v) if v is not None else None
def get_buying_power(self) -> float:
v = self.wait_value(self.state, "B5")
return float(v) if v is not None else 0.0
# ---------- 発注 ----------
def send_order(self, order_id: str, code: int, side: str,
qty: int, price: Optional[float] = None) -> str:
"""
発注セルに値を書き込んで注文を送信する。
order_id は一意であること。同じIDでの再送は受け付けられない(=冪等)。
"""
self.assert_ready()
if qty <= 0 or qty % 100 != 0:
raise ValueError(f"数量が不正です(100株単位): {qty}")
self.order.range("B2").value = order_id
self.order.range("B3").value = code
self.order.range("B4").value = side # "買" / "売"
self.order.range("B5").value = qty
self.order.range("B6").value = "指値" if price else "成行"
self.order.range("B7").value = price if price else ""
# 発注関数を評価させる
self.book.app.calculate()
# 結果セル(発注関数の戻り値)を待つ
result = self.wait_value(self.order, "B10", timeout=15.0)
log.info("発注結果 id=%s code=%s %s %s株 -> %s",
order_id, code, side, qty, result)
return str(result) if result is not None else "UNKNOWN"
# 本コードは本人利用に限り自由に使用可。再配布・商用利用・販売は禁止。
import json
import logging
import os
import time
from datetime import datetime, date
from pathlib import Path
import requests
from dotenv import load_dotenv
from rss_client import RssClient, RssError
load_dotenv()
WEBHOOK = os.getenv("DISCORD_WEBHOOK_URL")
STATE_FILE = Path("state/sent_orders.json") # 送信済み発注IDの永続化
KILL_FILE = Path("KILL") # このファイルを置くと停止する
WATCH = [7203, 6758, 9984]
MAX_ORDERS_PER_DAY = 5
CAPITAL = 1_000_000
RISK_RATE = 0.02
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
handlers=[logging.FileHandler("logs/bot.log", encoding="utf-8"),
logging.StreamHandler()],
)
log = logging.getLogger("bot")
def notify(msg: str) -> None:
log.info(msg)
if WEBHOOK:
try:
requests.post(WEBHOOK, json={"content": msg[:1900]}, timeout=10)
except Exception as e:
log.warning("通知失敗: %s", e)
def load_sent() -> set:
if STATE_FILE.exists():
return set(json.loads(STATE_FILE.read_text(encoding="utf-8")))
return set()
def save_sent(sent: set) -> None:
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(sorted(sent), ensure_ascii=False),
encoding="utf-8")
def make_order_id(code: int, side: str, when: date, seq: int) -> str:
"""再起動しても同じ条件なら同じIDになるよう決定論的に採番する"""
return f"{when:%Y%m%d}-{code}-{side}-{seq:03d}"
def should_buy(rss: RssClient, code: int) -> bool:
"""戦略ロジック。ここを差し替えて使う。"""
price = rss.get_price(code)
if price is None:
return False
# 例:終値が任意の閾値を上回ったら、など実際の条件に置き換える
return False # ← デフォルトでは発注しない(安全側)
def main() -> None:
rss = RssClient("bot.xlsx")
sent = load_sent()
today = date.today()
orders_today = sum(1 for x in sent if x.startswith(f"{today:%Y%m%d}"))
notify(f"bot起動 / 本日の発注済み: {orders_today}件")
while True:
# --- キルスイッチ ---
if KILL_FILE.exists():
notify("KILLファイルを検出したため停止します。")
break
# --- 日次上限 ---
if orders_today >= MAX_ORDERS_PER_DAY:
notify(f"本日の発注上限({MAX_ORDERS_PER_DAY}件)に到達したため停止します。")
break
try:
rss.assert_ready()
except RssError as e:
# 自動で復旧させない。人間が状態を確認するまで待機する
notify(f"[停止] RSSの状態異常: {e}")
break
for code in WATCH:
try:
if not should_buy(rss, code):
continue
price = rss.get_price(code)
if not price:
continue
# ロットは資金比リスクから算出し、100株単位に丸める
stop_width = price * 0.05 # 例:5%を損切り幅とする
shares = int((CAPITAL * RISK_RATE) / stop_width // 100 * 100)
if shares < 100:
log.info("%s: 必要リスク率に収まる数量が単元未満のため見送り", code)
continue
oid = make_order_id(code, "BUY", today, orders_today + 1)
if oid in sent:
log.info("%s: 送信済みのためスキップ", oid)
continue
result = rss.send_order(oid, code, "買", shares, price=None)
sent.add(oid)
save_sent(sent) # ← 発注直後に必ず永続化する
orders_today += 1
notify(f"発注 {oid} {code} 買 {shares}株 成行 -> {result}")
except Exception as e:
log.exception("銘柄%s の処理で例外: %s", code, e)
notify(f"[エラー] {code}: {e}")
time.sleep(30)
notify("bot終了")
if __name__ == "__main__":
main()
これが最も厄介な仕様です。作業中にうっかり別のExcelファイルを開くと、発注ステータスが「発注可」から落ちることがあります。botから見ると、何の前触れもなく発注が通らなくなります。
対策:発注前に毎回ステータスを確認し(サンプルコードの assert_ready())、異常なら自動で再有効化せずに停止して通知する。勝手に再有効化する実装は、状態を把握しないまま発注を再開することになり危険です。運用PCでは他のExcel作業をしない、というルールも併せて必要です。
銘柄コードをセルに書いた瞬間に現在値を読んでも、まだ取得中で 0 や空が返ります。これを価格として扱うと、意図しない価格で発注ロジックが動きます。
対策:値が確定するまで待つポーリング(サンプルの wait_value())を必ず挟み、タイムアウトしたらその銘柄はスキップする。「0円」を有効な価格として扱わないガードを入れてください。
Excelは自動再計算のタイミングを自分で決めます。「関数を書いたのに評価されない」「意図しないタイミングで再計算される」がどちらも起こります。
対策:book.app.calculate() で明示的に再計算を走らせる。そして発注の冪等性を発注IDで担保することで、意図しない再計算が発注に直結しないようにします。
Excel常駐が前提のシステムにとって、予期しない再起動は致命傷です。ポジションを持ったまま監視が消えます。
対策:アクティブ時間の設定、自動再起動の抑制、そして「再起動されても状態が復元できる設計」。サンプルコードで送信済み発注IDをJSONに永続化しているのはこのためです。メモリ上だけで状態を持つと、再起動=二重発注になります。
ExcelとPythonのビット数が食い違うと、xlwingsの接続で不可解なエラーが出ます。環境構築の最初に揃えておくのが最も安上がりです。あわせて、RSSアドインが対応するExcelバージョンかも確認してください。
自動売買で最も高くつく事故は、ロジックの誤りではなく二重発注です。意図の10倍のポジションを持ってしまえば、どんなに優れた戦略でも意味がありません。
サンプルコードでは KILL という名前のファイルが存在したら停止する実装にしています。これはリモートからでも、ファイルを1つ置くだけで確実に止められるという利点があります。プロセスをkillすると後始末が走らないため、「正常な終了経路で止める」ほうが安全です。
サーキットブレーカー(日次ドローダウン上限・連敗停止)の考え方は 自動売買botのサーキットブレーカー実装 が参考になります。
① should_buy() が常にFalseの状態で1日回し、事故が起きないことを確認する(サンプルはこの状態から始まります)
② 最小単元(100株)・1銘柄だけで実発注を1回試す
③ 発注金額上限を、許容できる最大損失額に設定する
④ 意図的にExcelを閉じて、botが正しく停止することを確認する
⑤ 意図的にPCを再起動して、二重発注が起きないことを確認する
④と⑤を試さずに本番運用に入る人が非常に多いのですが、実運用で必ず起きる事象です。起きてから対処するのでは遅すぎます。
国内で個人がPython自動売買を組む場合の選択肢を整理します。
| 項目 | 楽天証券 MARKETSPEED II RSS |
三菱UFJ eスマート証券 kabuステーションAPI |
SBI証券 |
|---|---|---|---|
| 発注API | あり(Excelアドイン) | あり(REST/WebSocket) | 公開情報なし |
| Python実装の素直さ | Excelを挟むため複雑 | HTTPのみ。素直 | — |
| 費用 | 無料 | Professionalプラン以上 | — |
| 常駐が必要なもの | MARKETSPEED II+Excel | kabuステーション | — |
| OS要件 | Windows | Windows | — |
| 安定性 | Excel依存の分だけ不利 | プロセスが1つ少ない | — |
コストをかけずに始めたい・Excelでの可視化も活かしたい → MARKETSPEED II RSS
実装をシンプルに保ちたい・本格的に作り込みたい → kabuステーションAPI
どちらもWindows常駐が必要な点は共通です。「Linuxサーバーで完全にヘッドレス運用したい」という要求は、国内株では現状かなり難しいと考えたほうがよいでしょう。この点、仮想通貨は取引所が純粋なREST/WebSocket APIを公開しており、Python自動売買の学習コストは圧倒的に低いです。自動売買の設計そのものを学ぶ目的なら、そちらから入るほうが早いという判断もあります。
SBI証券の状況については SBI証券で自動売買はPython自作できるのか で詳しく検証しています。
should_buy() が常にFalseを返す状態にしてあります。これは意図的な安全側の初期値です。投資は自己判断・自己責任でお願いいたします。本サイトの情報・ツール・ソフトウェアを参考・使用して、取引をされて何らかの損失を被られても、当サイトでは責任は一切負いません(間接的・付随的な障害・損失も含め一切の責任を負いません)。自己責任でご利用ください。
当サイトからダウンロードしたプログラム・EAは本人利用に限り完全無料でご利用いただけます。プログラムの全てまたは一部の複製・コピー・リバースエンジニアリング・改変・2次配布・商用利用・転売・販売等は一切禁止いたします。違反を発見した場合は法的処置を行います。
当サイト上に存在するソフトウェア・ツール・文章・画像・動画等の著作物を無断転載することを禁止します。引用の範囲を超えるものについては、法的処置を行います。
当サイトで配布するツール・ソフトウェアは無料提供ですのでサポートなどは行っておりません。万が一、ツールやソフトウェアに不具合や問題があっても当サイトでは修正・対応などができない場合があります。ご利用の環境でトラブルが発生しても当サイトでは助言することはできませんので自己責任でご利用ください。
当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。当サイト掲載情報やリンク先の掲載情報の合法性、正確性、道徳性、最新性、適切性、著作権の許諾や有無など、その内容については一切の保証を致しかねます。
当サイトは、ご訪問いただいた時間、訪問者のアクセス元のホスト名、IPアドレス、OSとブラウザのバージョン、リンク元のURL、モニターの情報等のパソコン環境情報を収集し、アクセス解析をしております。この目的は訪問者が検索サイトからご訪問いただいた場合、どのような検索で当サイトに来訪いただいたかの解析にのみ使用しており、第3者に公開はいたしません。また、個人を特定するものではありません。