一色からは「ChatGPTで株取引スクリプトを作ってバックテスト」という題でChatGPTを使って株取引ストラテジーを生成してシミュレーションしたことについて、かわさきからは「たんぱく質取ってますか?」という題でカロリーを考慮して鳥貴族のメニュー選びをアシストしてくれるGPTsを作成してみたことについて書きました。
@ITのDeep Insiderフォーラム【AI・データサイエンスの学びをここから】を担当しているDeep Insider編集部の一色とかわさきです。10月末に公開した編集後記から約3カ月ぶりですね。
編集者が記す「あとがき」である、この編集後記では、執筆/編集時には書けなかった小話や裏話、感想、ぜひ読者にも知ってほしいという話などを書いています。
@ITのDeep Insider編集部の編集長。2024年第22回『このミステリーがすごい!』大賞を受賞した『ファラオの密室』を読みました。僕はミステリー小説はほとんど読みませんが、舞台が古代エジプトの世界観だしミイラが主人公なのでファンタジー感もあって楽しめました。著者の白川尚史さんがPKSHA Technologyの共同創業者の一人だったので興味を持ったのが、読んだきっかけです。面白かったので、もうちょっと他のミステリー小説を読んでみようかと思っている今日この頃です。
おっ、奇偶ですね! ぼくもつい先日『ファラオの密室』を買ったところです(ただし、まだ積読状態)。
新NISAが始まって、投資信託のオルカンとS&P(500インデックス)が大人気みたいですね。僕も新NISAを楽しむ気で満々です。
つみたて投資枠はこれまでと同じ投資信託を毎月10万円分購入するつもりですが、「成長投資枠はどうしよう」と最近、悩んでいます。この枠もオルカンという人が多いようですが、僕は基本的に売買益を狙って個別株を中長期で保有したいと考えています。
そんなとき、YouTubeでChatGPTを使って株の売買ストラテジーを作っている人がいて面白かったので、自分もやってみました。今回紹介する方法は全て無料で実践でき、過去の株価で検証するバックテストもできます。
興味がある人は、ぜひ自分でも試してみてください。ただしある程度のプログラミング力は必要になります。
(※今回紹介する売買シミュレーションはあくまで仮想のものです。この方法を実際の株の売買に適用した場合に生じるいかなる損益や事象に対しても、読者自身の責任で対処していただくようお願いします。筆者や、筆者の所属会社、本情報が掲載されているメディアは、読者自身の行動から生じた結果について一切の責任を負いません。)
今回は、ChatGPTと、Web上のチャートツールであるTradingViewを使います。ChatGPTについては説明不要ですよね。TradingViewは、Pineスクリプトというプログラミング言語を持っており、独自のインジケータ(チャート上に何か描画するプログラム)やストラテジー(売買戦略のプログラム)を簡単に作成できます。前述の通りバックテストも完備しており、「作成したストラテジーを過去の株価の動きに対して使うと、どれくらいの損益になるか」をシミュレーションできます。
今回はTradingViewのBasicプラン(無料版)を使いますが、筆者はTradingViewを愛用しているのでPremiumプラン(有償のサブスクリプション版)を使っています。TradingViewのサブスクリプション価格は年間で155.40〜599.40ドルとちょっとお高めですが、毎年11月21日くらいから始まるブラックフライデーで大幅割引されるので、そのタイミングでサブスクリプション契約をアップデートするのがお勧めです。
まずChatGPT(3.5の無料版)で次のプロンプトを入力しました。株の用語が幾つか出てきますが、細かくは説明しないので分からなければ読み飛ばしてください。ゴールデンクロスについては、*1の注記で簡単に説明しています。
TradingView用のPineスクリプト(バージョン5)で株の売買ストラテジーを作成してください。信用取引はしません。
長期線が30で短期線が10のSMA(単純移動平均線)でゴールデンクロスしたら、5万円以内で買える最大株数で購入(entry)してください。1株単位で購入可能とします(ミニ株を想定)。
基本的に株は保有し続けて、ゴールデンクロスするたびに追加購入してください。
株価が1.05倍になったら全てを売却して利確(利益確定:exit)してください。
*1 「短期(例:5日間)移動平均線」が「中長期(例:25日間)移動平均線」を下から上に突き抜けることを「ゴールデンクロス」と呼び、株などの購入シグナルとなります。逆に上から下に突き抜けることを「デッドクロス」と呼び、売却のシグナルとなります。移動平均については、ぜひ「移動平均とは? SMA/WMA/EMAの違い」をご一読ください。
このプロンプトにより生成されたPineスクリプトに少し手を加えたのが以下のコードです。なお「ロング」は「購入」のことです。
//@version=5
strategy("Golden Cross Strategy", overlay=true, pyramiding=20)
// 「20」個の買いポジションが持てるよう設定
// [設定]ダイアログにパラメーターとして表示する入力変数
fastLength = input(10, title="短期線の長さ")
slowLength = input(30, title="長期線の長さ")
profitTakeLevel = input(1.05, title="株価が何倍になったら利確するか")
purchaseAmount = input(50000, title="1回の購入金額")
// デフォルトでは「20」個の買いポジション×1回「5万」円=購入予算は「100」万円
// デバッグ用のメッセージ出力例
//log.info("初期資金: {0}", strategy.initial_capital)
//log.info("現在資産: {0}", strategy.equity)
// 移動平均線の計算
fastSMA = ta.sma(close, fastLength)
slowSMA = ta.sma(close, slowLength)
// ロングエントリー条件
longCondition = ta.crossover(fastSMA, slowSMA)
// エントリー処理
if (longCondition)
// 1回の購入金額から購入できる株数を計算
maxShares = math.floor(purchaseAmount / close)
// 購入数が0より大きい場合は購入注文を発行
if (maxShares > 0)
strategy.entry("Buy", strategy.long, qty=maxShares)
// 利確条件
takeProfitCondition = close >= (profitTakeLevel * strategy.position_avg_price)
// 利確処理
if (takeProfitCondition)
strategy.close_all()
// プロット
plotshape(series=longCondition, title="Buy Signal", location=location.belowbar, color=color.green, style=shape.labelup, text="BUY")
plotshape(series=takeProfitCondition, title="Take Profit Signal", location=location.abovebar, color=color.red, style=shape.labeldown, text="TAKE PROFIT")
あとはこれをTradingViewのPineエディタに貼り付けるだけです。
スクリプトを保存すると自動的にコンパイルされて、エディタ上部にある[チャートに追加](もしくは[更新をチャートに反映])をクリックすると図1の上部にあるチャートに対して、購入タイミングの箇所に緑色の[BUY]が、利確タイミングの箇所に赤色の[TAKE PROFIT]が表示されるようになります。
ちょっとずつ買って、利益が大きくなったところで一気に売却する形をイメージしましたが、なかなか良さそうですね。画面下部の[ストラテジーテスター]にトレード履歴や利益の大きさや勝率などが表示されます。
あとは現実に売買するだけですが、もっと高パフォーマンスなストラテジーを作りたいと思っているので、実際の株の売買はしていません。あと、こういったテクニカル分析だけのストラテジーは「現実にはなかなかうまくいかないんだろうな」という気がしています。実際の売買を試したら、また編集後記で報告します。TradingViewは仮想資金によるペーパートレードもできるので、まずはこれで試す方がいいかもしれません。
一色さんが何か作っていたから、ぼくも編集会議では「つまらん!」といわれてしまったアレを今回は取り上げることにしました(笑)。
ああ、ChatGPT×鳥貴族ネタね。「つまらない」というより「ニッチだから、同じテーマでもっと広く読者ウケしそうなネタの方がいいかもね」というコメントだったと思います。
脱線しますが、自分の住んでいる街にも鳥貴族が1軒あり、夫婦で5年以上前に1回行ったら、あまりにうるさくて妻から「二度とトリキには行かない」と宣言され、それ以来、行ったことないです……。行ってみたいのですが。
特にグループ客が入りやすいお酒が飲めるお店は、1人でも声のボリュームが大きい客がいると、周囲の客も自分の声が聞こえるように大きい声を出さざるを得ない負のループを感じる(笑)。どうにかならないものか。
大学生時代にIT系出版社でアルバイトを始めて、そのまま就職という典型的なコースをたどったダメ人間。退職しても何か他のことをできるでもなくそのままフリーランスの編集者にジョブチェンジ。そしてDeep Insider編集部に拾ってもらう。お酒とおつまみが大好き。通称「食ってみおじさん」。今は筋肉増量期のはずが、脂肪が増加中かも(笑)。
皆さんが気になっているかどうかは全く分かりませんが、まずはダイエットのお話です。2023年の11月半ばに64kg台の体重に落としてからは、体重維持期として人並みの量のご飯を食べるようにしました(おおよそ2400kcal)。ですが、年末年始はやはり飲みの回数が増えるもので、たくさんのお誘いの中、体重は維持から微増という感じに。というわけで、これは筋肉を増やすいい機会だと決心し(維持するのを諦めて)、体重増量期に切り替えることにしました。やー、2日続けてラーメンを食べるとかしたらダメですよね(汗)。
おかげさまで体重は1.5kgほど増えてしまいましたよ。体脂肪率が減ってくれたら筋肉も付いているな! と思えるのですが、体脂肪率は変わらないまま21%前後をうろうろしています。
まあ、それでも朝の有酸素運動と週に2、3回の筋トレ(chocoZAP)は続けていますし、何もない日には2700kcal程度の食事を取るようにしている状況です。そして、そんな生活に欠かせないのが「たんぱく質」。週に一度は近所の鳥貴族にいっては、たんぱく質を摂取するという名目で焼鳥を食べています。
それはいいのですが、鳥貴族に行くと決めたはいいけれど、「今日は朝と昼にこれだけ食べちゃったから、夜は1000kcalしか食べられないなー」という日もあるわけです。鳥貴族が公開してくれている栄養成分表(PDF)をジッと見ながら、「ささみとレバーは必須として……」なんてことをするのも楽しいのですが、どうせなら機械にやってほしくないですか?
というわけで、登場するのがGPTsですよ。栄養成分表をXLSXファイルに変換して、GPTsに読み込ませれば、いつでもすぐに特定のカロリー内に収まるメニュー構成を考えてくれそうじゃないですか。たんぱく質の量を踏まえたメニューだって考えてくれるかもしれません。
そう思って、お正月休みにちょっと作ってみたのですが、どうにもこうにもうまくいかないなー、となりまして、ちょっと放置していたのですが、編集後記のネタに困って、再度、一から作り直すことにしてみました。名付けて「鳥貴族メニューアシスタント」さんです。
ですが、作り直してみたはいいもののやっぱりポンコツなんですよね。ま、それはともかく、ざっくりと何をしたかを書いていきましょうか。単にメニューをGPTsに与えるといっても、その前にしなければならないことが割とありました。
[カロリー量]列の追加はいわば手動でのビニングですね。GPTsを作成するときにビニングを指示してもよかったのでしょうが、うまくいくという保証もなかったので自前でやっちゃったという次第です。ただし、実際にこれが使われているかどうかは分かりません。
鳥貴族だけでこれだけ作業量があると、ファミレス全般とかはちょっと無理そうだね。
だいたいこんなことを事前に行っています。できたワークシートの一部はこんな感じです。
後はこれをGPTsの作成画面にある[Configure]タブでアップロードすれば準備は完了です。
後はGPTsの名前を決めたり、自動生成されるアイコン画像でよさげなものを決定したり、GPTsの概要を入力したり、動作を確認したりといったことをしていきます。その間にExcelワークシートの修正もしました([大カテゴリ]列の追加などは、GPTsの動作を修正する際に追加した列です)。
ここではプロンプトとして次のようなテキストを入力しました。GPTsの動作をカスタマイズする際には、このプロンプトに新たなテキストを追加したり、上で述べたようにExcelファイルを修正したりしながら、ファイルを読み込ませるたびにプロンプトも更新されたものを入力しています。
XLSXファイルを更新しました。これに伴いデータ構造が変更されています。処理の大まかな方針を以下に示します。
・ 「大カテゴリ」列の内容はカンマ「,」区切りの文字列なので、カンマを区切り文字として文字列を分割してリストに格納する
・ 「カテゴリ」列の内容もカンマ「,」区切りの文字列なので、カンマを区切り文字として文字列を分割してリストに格納する
・ 「メニュー名」列についても同様に、カンマを区切り文字として文字列を分割してリストに格納する
・ ユーザーが「焼鳥」「やきとり」「たれ焼」「デザート」「アルコール」「ささみ」「雑炊」「枝豆」のように個別の項目を入力したときには、それがまず「大カテゴリ」に含まれているかを調べ、含まれていなければ「カテゴリ」列に含まれているかを調べ、含まれていなければ「メニュー名」列に含まれているかを調べること
・ これらの列の内容とユーザー入力との比較には、ループを回して、リストの各要素に文字列が含まれているかをin演算子やcontainsメソッドなどを用いる
・ 「エネルギー」列の値の単位はkcal
・ 「たんぱく質」列の値の単位はg
・ 「脂質」列の値の単位はg
・ 「炭水化物」列の単位はg
・ ユーザーに提示するメニュー名には必ず「表示名」列、「エネルギー」列、「たんぱく質」列、「脂質」列、「炭水化物」列の内容を含めること
・ 例:「ささみ塩焼(112kcal、P:23.1g、F:0.8g、C:1.4g)」
・ 「カロリー量」列の値(大、中、小)を基に、ユーザーが指定した総カロリー数が1200kcal以下のときには小のメニュー項目を重点的に検索し、総カロリー数が1201〜1800kcalのときには小と中からメニューを選択し、1801kcal以上のときには小と中と大からメニューを選択します
・ ただし、ユーザーが特定のメニューを指定したときには、そのメニューは必須のものとして上の制約を課さない
・ 常に同じメニューを選択することがないように、ある程度のランダム性を持ってメニューを選択する
・ ユーザーが特定のメニューを指定したときには、「メニュー名」列の値と順次照合をする
・ ユーザーが「焼鳥」「やきとり」などと入力したときには、「大カテゴリ」列の内容をカンマを区切り文字として分割したリストの要素と個別に比較をしてマッチするかどうかを確認してください。単純に==で比較するのはやめること
現状はこんな感じのプロンプトを入力している状態ですが、既に述べた通り、このアシスタントはちょっとポンコツ風味です。実際に対話しているところを見てください。
上の画面ではカロリーが少ない焼鳥メニューを教えてくれとお願いをしているところです。このようなときには[大カテゴリ]列→[カテゴリ]列→[メニュー名]の順序でユーザーが入力した語句を検索していくようにプロンプトでは指示をしていますが、うまく答えられませんでした。そのため、データ構造が分かっている筆者が直々にこういう処理をすれば見つかるはずだと指示をすることで、適切な回答が得られました。
次の画像はもっとダメです。
今度は「1000kcalの範囲でアルコールを2杯と、焼き鳥を4品、スピードメニューから1品」というメニュー構成を考えてくれといったところ、スピードメニューというカテゴリは見付けられないし、焼き鳥は4品といっているのに2品しか提案されていないというかなりダメな結果になっています。
とまあ、データを与えて、ある程度の方針を指示しても、それほど簡単に思った通りの振る舞いをしてくれるGPTsが出来上がるわけじゃないんだなということが判明したのでした。何より、遅いのです。GPTsの振る舞いをチャットベースで修正しながら、のんびりとお酒と料理が届くのを待つのには向いているかもしれませんが、実用的じゃなかったなぁというのが今回の感想です。
これからは、のんびりと気長にこのGPTsの動作を改善していこうと思っています。なお、現在は自分だけが使える設定にしていて、公開する予定はありません(使いたい人もいないでしょうが)。
真面目にコメントすると、ステップバイステップで考えさせるための回答例も、[Configure]タブの[Instructions]にあらかじめ入力しておくか、回答例集みたいなファイルをKnowledgeにアップロードすれば、回答結果が改善しそうな気がしました。背後にあるLLMが飲食物のカロリー計算の方法を学んでいないので、できないのかなと。どうやってカロリーを計算するかをまず教える必要があるかもしれません。
あとカロリー計算をGPTに任せるのは不安なので、カロリー計算はルールベースのサービスとして作成し、そのサービスのAPIとActionsで連携するのが最適だろうなと思いました。ただしそこまでやると、遊びの域は逸脱しちゃうかもですし、そもそもカロリー計算をChatGPTにやらせたいので始めたと思うので趣旨も変わるか(笑)。
Copyright© Digital Advantage Corp. All Rights Reserved.