TradingViewの外でPine Scriptを安定して動かしたい。そう思って作り始めたのが、Pine ScriptをCLIで実行できるようにする
Pinescriptionだ。この記事では、なぜ作ったのか、どんなルールでagentに実装を進めさせたのか、そして人間がどこを握るべきだったのかをまとめる。
TL;DR
- Pine Scriptの最新言語仕様をCLIで扱えるようにしたくて
Pinescriptionを作り始めた - 設計の起点は
AGENTS.mdに置き、言語仕様・未実装範囲・Goの公開インターフェースを先に固定した - 実装は agent に大きく任せたが、レビュー、コミット判断、バグの切り分けは人間が持った
- Benchmark を早い段階で書き始めたことで、後のリファクタで性能が崩れるのを防ぎやすくなった
- Demo は別モデルに一気に生成させ、最後だけ OpenCode で仕上げた
この記事の対象
- GUIよりCLIで開発したい人
- coding agent を使って中規模以上の実装を前に進めたい人
-
DSL、compiler、runtime の境界をどう決めるかに興味がある人(コードをそのまま読んでください
)
プロジェクト URL
- Pinescription: https://github.com/woodstock-tokyo/pinescription
- Pinescription DEMO: https://github.com/woodstock-tokyo/pinescription-demo
なぜPine ScriptをCLIで動かしたかったのか
Pine Scriptは、TradingViewで指標、チャート、売買戦略を書くための言語として広く使われている。Webhook 経由で broker API とつなぎ、発注まで持っていく使い方も一般的だ。さらに、TradingView系の chart library は国内外の多くの取引プラットフォームで使われている。
ただ、実運用を考えると「TradingViewを開かないと開発しづらい」「Webhook の取りこぼしが気になる」「複数の指標を回すために高額なサブスクリプションに寄りたくない」といった不満が残る。
そこで、Pine Scriptの最新仕様を実装しつつ、純粋なCLI環境で動かせる実行エンジンを作ろうと考えた。回測機能は別途必要になるが、少なくとも「Pine Scriptを自分の実行環境に持ち込む」という目的にはかなり近づける。
使ったモデルの雑感
OpenCodeで実装を進める中で、主に比較していたのは GPT-5.3 Codex と Claude Opus 4.6 だった。
| 観点 | GPT-5.3 Codex | Claude Opus 4.6 |
|---|---|---|
| OAuth Login | 安価。サードパーティーアプリのOAuthログインを許容しやすい | 高価。Claude Code に寄りやすい |
| 確認の頻度 | 確認を多めに挟む | 一気に完了まで持っていきやすい |
| 速度 | 速い | 遅い |
雑にまとめると、Codex は速くて細かく確認を取りやすい。Opus は遅めだが、一発で深く進めたい場面では強い。どちらが優れているというより、作業の性質で使い分けるのが現実的だった。
なぜ OpenCode を使ったのか
一番大きい理由は、自分がGUI中心のコーディング体験をあまり好きではないからだ。opencode-cli なら CLI のまま作業を進められるし、Provider を簡単に切り替えられる。
- OpenAI
- Anthropic
- Copilot
- OpenCode Zen
- Custom Local LLM
Provider を切り替えたかったのには、かなり実務的な理由がある。自分は OpenAI Pro を使っているが、週ごとの上限に達したら会社の Copilot や Anthropic の個人枠に切り替える。そこも使い切ったら OpenCode Zen の無料モデル、あるいはローカルの Qwen に落とす。こういう運用ができるだけでもかなり便利だった。
加えて、OpenCode 独自の plugin と周辺エコシステムが強い。
opencode-dcpoh-my-openagentopencode-scheduleropencode-elf
opencode-elf はプロジェクト配下とグローバルに SQLite database を作って cache を持つ。opencode-dcp は context を圧縮しつつ自動で掃除してくれる。これでモデルが扱える情報量を底上げできる。oh-my-openagent は agent prompt を model ごとに最適化する役割が大きく、Codex の出力をかなり扱いやすくしてくれた。
このプロジェクトを始めた動機
このプロジェクトは、会社の正式な製品計画の中から出てきたものではない。日々の開発の合間に余っていた計算資源を使って、数年前にOSSへ寄稿した core library を拡張しつつ、LLM 開発の限界を試したくて始めた。
将来どう会社の製品に組み込むかはまだここでは書かない。ただ、実験として始めたものが、そのまま実用的な資産に育つ感触はかなりあった。
自分がいる Woodstock株式会社 は、一般ユーザー向けの米国株取引ツールを開発している。手数料や為替、出金コスト、税務面の負担をできるだけ下げながら、初心者でも少額から米国株に参加しやすくするのが狙いだ。小さなチームなので、AI を使った開発は大手の開発体制に対抗するための現実的な武器にもなっている。
最初に決めたこと:AGENTS.md を先に書く
ここが一番重要だった。Claude Code の CLAUDE.md ではなく、自分は AGENTS.md を使ってプロジェクトの境界を定義した。
最初に置いたのは、プロダクト名と説明だ。
# PineScript Compiler in Golang - Pinescription
Pinescription is a compiler that translates PineScript into bytecode which could be executed by a virtual machine.
It implements version 6 of Pinescript, and supports part of the features in PineScript.
次に、Pine Script の公式ドキュメントへのリンクを AGENTS.md に入れた。これは「何を正とするか」を agent に明示するためだ。言語仕様を外部参照として持たせておくと、実装時の迷いがかなり減る。
さらに、実装状況を追跡するために、対応済み機能と未対応機能のセクションを先に作った。
1. [Pinescript features supported](#pinescript-features-supported)
2. [Pinescript features not supported](#pinescript-features-not-supported)
!!Add implemented features HERE from the manual if you find any
## Pinescript features supported
- …
- …
!!Add any missing features HERE from the manual if you find any
## Pinescript features not supported
- …
- …
初期方針として描画機能は内蔵しない前提だったので、未対応機能も明示した。
## Pinescript features not supported
- strategy functions (like `strategy.entry`, `strategy.exit`, etc.)
- alert functions (like `alert`, `alertcondition`, etc.)
- Plotting functions (plot, plotshape, plotchar, etc.)
この時点で「何をやらないか」を書いておくのはかなり効く。agent は放っておくと実装範囲を広げがちなので、未対応を明文化しておくと暴走しにくい。
Go のインターフェースも先に固定する
言語仕様だけでは足りない。実際に使える形にするには、Compiler と Runtime の外部インターフェースも先に決めておく必要がある。
そこで、AGENTS.md に以下のような Go インターフェースを入れた。
## Golang interface and functions
- `Compile(pinescript string) ([]byte, error)`: Compiles the given Pinescript code into bytecode. Returns the bytecode and any compilation error.
- `Execute(bytecode []byte) (interface{}, error)`: Executes the given bytecode and returns the result and any execution error.
- `RegisterFunction(name string, function func(args ...interface{}) (interface{}, error))`: Registers a custom function that can be called from Pinescript. The function takes a variable number of arguments and returns a result and an error.
- `RegisterMarketDataProvider(provider Provider)`: Registers a market data provider function that can be called from Pinescript to get the latest price of a symbol. The function takes a symbol as an argument and returns the price and any error.
- `Provider` Interface: An interface that defines methods `GetSeries(seriesKey string) (SeriesExtended, error)`, `GetSymbols() ([]string, error)`, and `GetValuesTypes() ([]string, error)` for fetching market data series and supported value types. `seriesKey` follows `symbol|value_type`.
この段階で公開面を固めておくと、後から agent が内部実装をどれだけ動かしても、「どこを守るべきか」がぶれにくい。
守らせたルール
最後に最低限の Must を入れた。
## Must
- Write test cases for all the features supported and not supported, and make sure they are passing.
- Write documentation for all the features supported and not supported, and make sure they are clear and concise.
正直、この種のルールは coding agent の実装側にもある程度入っている。ただ、自分のプロジェクトでは明文化しておいたほうがレビューしやすかった。
開発フロー
実際の開発フローはかなり単純だった。初期構成は次の通りだ。なお、後半では別の設定や追加の prompt も使っているので、ここで書くのはあくまで最初の形だと思ってほしい。
1. まずは planning をさせる
opencode-cli を起動したら、思考モードは xhigh に固定し、まず Prometheus (Plan Builder) モードに切り替える。
Prometheus は、oh-my-openagent における計画担当のエージェントです。タスクの内容を整理し、不明点や前提を明確にしたうえで、実行前に構造化された計画を立てます。
opencode .
その上で AGENTS.md を読ませ、計画を作らせる。ここでは、agent は実装前にかなり細かく必要条件を聞いてくる。そこに答えていくと、最終的に作業ステップの一覧ができる。
この段階で大事なのは、「いきなり書かせない」ことだ。最初に計画を作らせたほうが、後の破綻が少ない。
2. 実装フェーズに入る
計画が固まったら、Sisyphus (Ultraworker) を Anthropic 系で、あるいは Hephaestus (Deep Agent) を OpenAI 系で使い、実装を進める。
Sisyphus は、oh-my-openagent における中核となるオーケストレーション担当のエージェントですが、必要に応じて自ら実装も行います。タスク全体を統括し、必要に応じて他の専門エージェントを連携させながら作業を進めます。特に Anthropic 系モデル向けに最適化された設計が特徴です。
Hephaestus は、実装を担う実働型エージェントです。コードベースを調査し、実装方針を見極めながら、実際のコーディングをエンドツーエンドで進めます。こちらは GPT 向けに最適化された GPT ネイティブなエージェントとして位置づけられています。
ここでは agent にかなり自由を渡すが、完全放置はしない。止まるたびに AGENTS.md の更新内容とコード差分を見る。問題がなければ手動で commit する。曖昧な部分や怪しい挙動があれば、その場でテストを書かせて直させる。
流れとしては、ほぼ普通の code review と同じだ。違うのは、実装者が人間ではなく agent だというだけになる。
3. AI review は補助、最終判断は人間
GitHub に載せているプロジェクトなら、codepilot のような AI review を併用して意味レベルのバグを拾うこともできる。ただし、ロジックの欠落や設計上のまずさは、結局人間が判断するしかない。
ここはかなり重要で、生成AIに実装を任せても、正しさの責任までは移せない。agent は加速装置としては強いが、品質保証の主体にはならない。
実コードを早めに食わせる
ある程度まで実装が進んだら、抽象的な spec だけで回すのをやめて、実際のコードを投げてテストさせる。そうすると、欠けていた API がテスト修正の過程で自然に補われることが多い。
このフェーズに入ると、agent の強みがかなり出る。仕様からの実装よりも、具体例から不足分を埋めていくほうが安定する場面が多かった。
早い段階で Benchmark を書いた理由
開発中は、すでに実装した API を見ながら code agent に Benchmark を書かせた。目的は単純で、将来のリファクタリングで性能が大きく落ちないようにするためだ。
手書き開発では「早すぎる最適化は悪」と言われがちだが、agent と一緒に育てるプロジェクトでは少し事情が違う。後から性能改善をまとめてやらせると、プロジェクト全体が大きくなった時に大量の token を消費する。しかも、その時点では設計上の制約が固定されてしまっていることも多い。
だから、このプロジェクトでは Benchmark をかなり早い段階から書いた。結果として、性能の議論そのものが実行フローやアーキテクチャの決定に直結した。
Demo は別 bot に一気に作らせた
Demo プロジェクトはかなりラフに作った。目的は完成度の高いプロダクトではなく、あくまで機能展示だったからだ。
example と test case の中に必要な実装はすでにかなり揃っていたので、手元にあった Claw bot に「ゼロからフロントエンドとバックエンド一式を作ってほしい」と一文で投げて生成させた。生成後に clone し、OpenCode で数カ所のミスを直し、docker-compose も追加して仕上げた。
このあたりは、agent をどこまで信用するかの線引きがわかりやすい。検証コストが低いものは大胆に任せ、核心部分は人間が握る。そのほうが全体の速度が出る。
生成AIで開発するときに人間が握るべきもの
今回のプロジェクトで改めて感じたのは、agent に全部やらせるのではなく、境界を決める側に人間が回るべきだということだ。
自分が人間として握っていたのは主に次の4つだった。
- 何を作るか
- 何を作らないか
- どのインターフェースを守るか
- どの時点で commit してよいか
逆に、agent に大きく任せたのは以下だった。
- 実装のたたき台を出すこと
- テストケースを増やすこと
- ドキュメントの追記をすること
- Benchmark を回して改善案を出すこと
- Demo を素早く形にすること
この分担がはまると、agent はかなり強い。
まとめ
Pinescription を作りながら学んだのは、「agent にコードを書かせる」こと自体よりも、「agent が迷わない境界を先に作る」ことのほうが重要だということだった。
-
AGENTS.mdで仕様と未対応範囲を明示する - 公開インターフェースを先に固定する
- planning と implementation を分ける
- 人間はレビューと意思決定に集中する
- Benchmark を早めに置いて、後で苦しまないようにする
この流れを取るだけで、coding agent はかなり実用的になる。少なくとも、自分にとっては「試しに触るツール」ではなく、日常開発の中で本当に戦力になる存在だった。
同じように CLI ベースで agent 開発を回している人がいたら、どんな AGENTS.md を書いているのかぜひ知りたい。もっといい境界の切り方があれば試してみたいし、Pine Script 系の実装で詰まりやすい点があれば、それも共有してもらえるとうれしい。
- Pinescription: https://github.com/woodstock-tokyo/pinescription
- Pinescription DEMO: https://github.com/woodstock-tokyo/pinescription-demo
本記事はZennにも同内容で掲載しています。