Type/to search
2
Follow
494
Followers
Polymarket マルチロールスマート分析取引システムの設計と実装
Discussions
Created 2026-04-03 17:54:27  Updated 2026-04-13 16:31:58
 0
 371

img

戦略背景

今年の初め、Polymarketで一晩で家一軒分の大金を稼いだという噂がコミュニティで広まりました。興味本位で具体的な取引フレームワークを検索してみたところ、すべて見出しだけで、一行のコードもありませんでした。仕方なく、自分で作り始めることにしました。

img

最初はまったく手掛かりがありませんでした。Polymarketは現在最大規模の分散型予測市場プラットフォームで、政治、スポーツ、マクロ経済など、予測可能なほぼすべてのイベントをカバーする6万以上の銘柄が上場されています。各銘柄は本質的にバイナリーオプション契約で、結果が発生すれば価格は1に収束し、発生しなければ0になります。最も儲かる方法といえば、もちろん内部情報です。しかし、一般の人がマスクが来週何回ツイートするか、FRBが次回利下げするかどうかを知る術はなく、相場を監視・分析して意思決定する時間や余裕もありません。

そこで、別の視点から考えました。情報を追うのではなく、資金を追うのです。情報優位を持つ人々は事前にポジションを構築し、その買い行動は価格と出来高に必ず痕跡を残します。「内部情報」を知らなくても、資金の動きを観察することで、誰かが静かにポジションを構築しているかどうかを推測できます。この考えに沿って、マルチロール・インテリジェント分析取引システムを構築し、ローソク足でのシグナル検出、センチメント分析による検証、AIによる判断、自動発注とリスク管理を一貫したパイプラインとして統合しました。

img


システムアーキテクチャ

システム全体は発明者ワークフロー上で動作し、メインラインとサブラインの2つの並行スケジューリングパスに分かれており、両者は完全に分離され、互いにブロックしません。

スケジューリングパストリガー頻度担当モジュールコアタスク
取引メインライン10分ごと銘柄選別 → ループ処理 → AIエージェント → 取引実行機会の発見、注文・ポジション構築
取引サブライン30秒ごと利確・損切りポジション監視、トレーリングストップ、満期回収

メインラインは「機会を見つけ、ポジションを開く」役割を担い、各ラウンドの実行は4つの直列フェーズで構成されます。初期フィルタリング → ローソク足異常検出 → ニュース検索 → AIマルチロール分析。最終的に買いシグナルを出力し、注文実行に移ります。サブラインは「既存ポジションの管理」のみを担当し、30秒間隔で継続的に実行されます。メインラインに進行中のタスクがあるかどうかに関わらず、サブラインは停止しません。

この分離設計は、実際の問題を解決します。予測市場の価格変動は連続的ですが、銘柄分析は計算負荷が高く遅延が発生します。損切り監視をメインラインに組み込むと、AI分析が遅れた場合、損切りの応答が著しく遅れます。独立したスケジューリングは、必要なエンジニアリング上の妥協点です。


各モジュールの詳細

初期フィルタリング:まずは低品質な銘柄を排除

6万もの銘柄を逐一分析することは不可能なため、最初の関門は純粋なルールベースのフィルタリングです。目的はただ一つ、明らかに検討に値しない銘柄を素早く除外し、以降の計算リソースを真に価値のある銘柄に集中させることです。

フィルタリング条件は以下の通りです。流動性の下限(低すぎると売買が困難)、24時間出来高の下限(ほぼ取引されていない銘柄は参考価値が低い)、売り気配と買い気配のスプレッドの上限(スプレッドが大きすぎるとスリッページコストが高い)、競争度の上限、そして価格範囲。つまり、市場の暗黙確率が5%~50%の銘柄のみに注目します。高すぎるものはすでに十分に価格に織り込まれており、上昇余地が限られています。低すぎるものは信頼性に疑問があり、裁定機会というより宝くじに近いです。

フィルタリング後、同じイベントには通常YesとNoの両方向が上場されています。戦略では、より低い価格の方向のみを保持します。低価格側のオッズの弾力性が高く、潜在的なリターンが大きいためです。

javascript
let truePrice = parseFloat(outcomePrices[outcomeIndex] || 0) if (truePrice < CONFIG.MIN_TRUE_PRICE || truePrice > CONFIG.MAX_TRUE_PRICE) continue let isActive = ( info.active === true && info.approved === true && info.closed === false && info.archived === false && info.acceptingOrders === true && info.pendingDeployment === false ) if (!isActive) continue let spreadPct = (ask - bid) / ask if (spreadPct > CONFIG.MAX_SPREAD_PCT) continue // 同じイベントでは、より低い価格の方向のみ保持 if (!marketMap[baseSymbol]) { marketMap[baseSymbol] = entry } else if (truePrice < marketMap[baseSymbol].price) { marketMap[baseSymbol] = entry }

すべてのフィルタリング閾値は $vars 変数で外部パラメータとして公開されており、自身のリスク選好や資金規模に応じてコードを変更せずに自由に調整できます。

img

ローソク足異常検出:資金の痕跡を探す

粗いフィルタリングの後、通過した各銘柄について60分足の直近240本のローソク足を取得し、5種類の異常検出を実行します。これが戦略のコアシグナル層であり、システム全体を「イベントを直接AIに分析させる」方法と最も根本的に区別する点です。私たちはイベント結果を予想するのではなく、資金の行動を探すのです。

緩やかな上昇:過去120本のローソク足の合計上昇率が5%を超えるが、1本あたりの最大上昇率は1.5%未満。この特徴は、資金が意図的にペースを抑え、注目を避けながらポジションを構築する姿勢を表しています。

出来高の線形増加:直近60本の出来高系列に対して線形回帰を適用し、傾きが正でR²が0.5以上の場合、出来高が持続的かつ安定的に増加していることを示し、ランダムなパルスではありません。R²が鍵であり、単一の急増で他は沈静化するようなノイズを除外します。

押し目の縮小:過去の高値以降の各押し目の最大下落幅を集計し、直近の平均押し目が初期の60%未満であれば、売り圧力が減少し、ポジションが安定してきており、誰かが買い支えていることを示します。各押し目期間中は継続的に最安値を追跡し、完全な押し目幅を計算の基礎とします。

レンジブレイクアウト:MA60とMA120の乖離が2%未満(レンジ相場)で、現在価格がMA60を3%超えて上回っている場合、過去のレジスタンスラインを突破したと判断します。

出来高の急増:直近5本の平均出来高が過去60本の基準出来高の2.5倍を超える場合、資金の加速的な参入と判断します。

javascript
function linearRegression(values) { let n = values.length let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0 for (let i = 0; i < n; i++) { sumX += i; sumY += values[i] sumXY += i * values[i]; sumX2 += i * i } let slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX) let intercept = (sumY - slope * sumX) / n let meanY = sumY / n let ssTot = 0, ssRes = 0 for (let i = 0; i < n; i++) { ssTot += Math.pow(values[i] - meanY, 2) ssRes += Math.pow(values[i] - (slope * i + intercept), 2) } let r2 = ssTot === 0 ? 0 : 1 - ssRes / ssTot return { slope, intercept, r2 } } function detectAnomaly(closes, volumes) { if (closes.length < 60) return null let anomalies = [], score = 0 let slice120 = closes.slice(-Math.min(120, closes.length)) // ── 异常1:缓慢爬升 ────────────────────────────────────── let totalChange = (slice120[slice120.length - 1] - slice120[0]) / slice120[0] let maxSingle = 0 for (let i = 1; i < slice120.length; i++) { let change = Math.abs(slice120[i] - slice120[i - 1]) / slice120[i - 1] if (change > maxSingle) maxSingle = change } if (totalChange > 0.05 && maxSingle < 0.015) { score++ anomalies.push(`缓慢爬升 总涨${(totalChange * 100).toFixed(1)}% 单根最大${(maxSingle * 100).toFixed(2)}%`) } // ── 异常2:成交量线性增长 ───────────────────────────────── let volSlice = volumes.slice(-60) let volReg = linearRegression(volSlice) if (volReg.slope > 0 && volReg.r2 > 0.5) { score++ anomalies.push(`成交量线性增长 斜率${volReg.slope.toFixed(4)} R²${volReg.r2.toFixed(2)}`) } // ── 异常3:回调收窄(追踪每次回调最低价,计算完整深度)──── let pullbacks = [], localHigh = closes[0] let inPullback = false, pullbackLow = closes[0] for (let i = 1; i < slice120.length; i++) { if (closes[i] > localHigh) { if (inPullback) { pullbacks.push((localHigh - pullbackLow) / localHigh) inPullback = false } localHigh = closes[i] } else if (closes[i] < localHigh * 0.99) { if (!inPullback) { inPullback = true; pullbackLow = closes[i] } else if (closes[i] < pullbackLow) { pullbackLow = closes[i] } } } if (pullbacks.length >= 3) { let first = pullbacks.slice(0, Math.floor(pullbacks.length / 2)) let last = pullbacks.slice(Math.floor(pullbacks.length / 2)) let firstAvg = first.reduce((a, b) => a + b, 0) / first.length let lastAvg = last.reduce((a, b) => a + b, 0) / last.length if (lastAvg < firstAvg * 0.6) { score++ anomalies.push(`回调收窄 早期${(firstAvg * 100).toFixed(2)}% 近期${(lastAvg * 100).toFixed(2)}%`) } } // ── 异常4:横盘突破 ─────────────────────────────────────── let ma60 = closes.slice(-60).reduce((a, b) => a + b, 0) / 60 let ma120 = closes.length >= 120 ? closes.slice(-120).reduce((a, b) => a + b, 0) / 120 : ma60 let currentPrice = closes[closes.length - 1] if (Math.abs(ma60 - ma120) / ma120 < 0.02 && currentPrice > ma60 * 1.03) { score++ anomalies.push(`横盘突破 MA60=${ma60.toFixed(3)} MA120=${ma120.toFixed(3)} 当前=${currentPrice.toFixed(3)}`) } // ── 异常5:成交量突增 ───────────────────────────────────── let recentVol = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5 let baseVol = volumes.slice(-65, -5).reduce((a, b) => a + b, 0) / 60 if (baseVol > 0 && recentVol > baseVol * 2.5) { score++ anomalies.push(`成交量突增 近5根均量是基准${(recentVol / baseVol).toFixed(1)}倍`) } return { score, anomalies } }

異常が1つヒットするごとに1点が加算され、満点は5点です。合計点が2点以上の場合のみ、次の段階に進みます。単一のシグナルはランダムノイズである可能性がありますが、複数が重なることで、資金の動きに一定の信頼性があることを示します。これがKライン検出ロジック全体の核となる仮定です。

ニュース検索:異常に対するファンダメンタルズの説明を見つける

Kラインのシグナルは「資金が動いている」という証拠ですが、なぜ資金が動いているのか、その動きに根拠があるのかを検証するには、情報面での裏付けが必要です。このステップでは、Kラインが異常を示す各銘柄についてニュースを検索し、対応するイベントが存在するかどうかを確認します。

ここではBrave Search APIを使用します。月間2000回の無料枠があり、10分ごとのメインラインのペースであれば十分に賄えます。検索キーワードはsymbolから抽出し、方向接尾辞と区切り記号を除去した後、読みやすいイベント記述語に変換します。

javascript
const q = item.symbol .replace("_USDC.No", "") .replace("_USDC.Yes", "") .replace(/-/g, "+") const rawResponse = HttpQuery( "https://api.search.brave.com/res/v1/web/search?q=" + q, { method: "GET", headers: { "X-Subscription-Token": CONFIG.BRAVE_API_KEY } } ) // すべての返却セクション(ウェブ、ニュースなど)を走査し、構造化フィールドを統一的に抽出する Object.keys(data).forEach(key => { const section = data[key] if (section && section.results && Array.isArray(section.results)) { section.results.forEach(r => { news.push({ type: key, title: r.title, description: r.description, age: r.age }) }) } })

検索結果のニュースとKラインのデータをまとめて、次のステップであるAI分析へ送る——両方のラインを同時に検証することで、片方だけ見るよりもはるかに信頼性が高まる。

AIマルチロール分析:4つの視点で自己説得を防ぐ

データが準備できたらAI分析ノードに入り、モデルが自身のニーズに応じて選択する。プロンプトでは4つの独立した役割を定義しており、これはシステム全体の根幹をなす設計判断であり、詳しく説明する価値がある。

1つのAIにKラインとニュースの両方を同時に見せると、両者を相互に検証し合い、表面上は合理的だが実際には独立した検証を欠いた結論を導き出しやすくなる——資金シグナルでニュースを裏付け、さらにニュースで資金シグナルを裏付けるという循環に陥り、最終的に「買いだと思う」という自己強化された結果を出力する。役割を分離することで情報源の独立性を強制する。各役割は自分のデータだけを見て、自分の軸の結論だけを出力し、最終的な意思決定役割は結論だけを見てプロセスは見ない。

役割1:K線資金アナリスト。ニュースは見ず、価格と出来高の異常だけを見て、実際の資金が継続的に買い入れているかどうかを判断し、資金シグナルの強さ(強/中/基準)を出力する。

役割2:ニュース世論アナリスト。K線は見ず、情報面のみを評価し、まだ市場価格に織り込まれていない好材料が存在するかどうかを判断し、ニュースサポートのレベル(強/弱/無)を出力する。

役割3:価格評価アナリスト。前の2つの役割の結論を統合し、イベントの真の確率区間を推定し、現在の市場価格と比較して、割安/妥当/割高の評価を下す。この役割には、満期日による重み付けルールが組み込まれている:残り7日以上なら通常の推定;3日以内かつ市場価格が0.15を超える場合は、確率区間を強制的に狭め、「大幅割安」の出力を禁止する。理由は明白で、満期が近づくと市場参加者が保有するリアルタイム情報はAIの学習データよりも完全であり、その時点でのAIの判断は市場価格に譲歩すべきだからである。

役割4:最終意思決定アナリスト。決定マトリックスに従って前の3つの役割の結論を集約し、最終的なアクションを出力する。マトリックスのロジックは:資金シグナルが前提、ニュースが検証であり、両者が共同で信頼度を決定する。ただし、信頼度がどれほど高くても、価格評価が「割安」に達しない限り、一切操作を行わない。

ここでのニュース検証は情報を追うことではなく、資金行動にすでにファンダメンタルズの裏付けがあるかどうかを判断するためである。インサイダー資金がポジションを構築するとき、情報は公開ニュースには現れない。しかし資金シグナルにニュースの裏付けが加わる場合、情報は拡散の中盤にあり、市場にはまだ価格決定の遅れが残っていることを示す——これこそが戦略が本当に捉えようとしている窓である。

資金シグナル \ ニュースサポート強い検証弱い検証検証なし
強(スコア4~5)買い、信頼度=高買い、信頼度=中操作しない
中(スコア3)買い、信頼度=中様子見操作しない
基準(スコア2)様子見操作しない操作しない

AIの出力形式は厳格なJSONであり、各役割の結論、真の確率区間、価格評価、最終決定、信頼度、リスクリストを含み、直接下流の実行ノードで消費される。

取引実行:最良のものだけを買う

AIが各バッチの銘柄を順にスコアリングした後、実行レイヤーはすべての買いシグナルを注文するわけではない——システムは最高スコアのものだけを選ぶ。並べ替えルールは順に:信頼度(高 > 中 > 低)→ 価格評価(大幅割安 > 割安)→ 真の確率区間の幅(狭いほど判断が確定的)。

javascript
const buySignals = signals.filter(s => s.signal === 'buy') bestSignal = buySignals.sort((a, b) => { const confDiff = (confidenceRank[b.confidence] || 0) - (confidenceRank[a.confidence] || 0) if (confDiff !== 0) return confDiff const assessDiff = (assessmentRank[b.priceAssessment] || 0) - (assessmentRank[a.priceAssessment] || 0) if (assessDiff !== 0) return assessDiff // 区間が狭いほど確定的、狭い方を優先 const aWidth = (a.trueProb.max || 0) - (a.trueProb.min || 0) const bWidth = (b.trueProb.max || 0) - (b.trueProb.min || 0) return aWidth - bWidth })[0]

選定された銘柄について、リアルタイムで板のAsk価格を取得し、残高が十分であることを確認し、指値注文で提出する。注文提出後、状態をポーリングし、最大30秒待機し、タイムアウトした場合は注文をキャンセルし、未約定注文の滞留を防ぐ。そのラウンドのAsk価格に構成シェアを掛けた値が1 USDCの最小注文閾値を下回る場合、システムは自動的に ceil(1/askPrice) で最小取引可能シェアに切り上げる。

ストップロスモジュール:トレーリングストップロス+絶対的な下限

サブラインは30秒ごとに実行され、ロジックは2段階:まず期限切れの償還を処理し、次にアクティブなポジションに対してトレーリングストップロスチェックを行う。

Polymarketには特別なメカニズムがある——コントラクトが満期になっても資金は自動的に口座に戻らず、能動的にリデーム(償還)を呼び出す必要がある。戦略は各ラウンドのサブラインの開始時に、償還可能なポジションを一括でスキャンし、資金をバルクで解放し、資金の長期ロックを避ける。

トレーリングストップロスの核心は、デュアルトレーリングストップロスラインの設計である:トレーリングストップロスライン(過去最高Bid価格 - 固定下落幅 LOSS_AMOUNT)と絶対的な下限ライン(エントリー価格×50%)。実際のストップロス価格は両方の大きい方の値を取る。

javascript
function calcStopPrice(entryPrice, maxPrice) { const trailingStopPrice = maxPrice - CONFIG.LOSS_AMOUNT const absoluteStopPrice = entryPrice * CONFIG.FALLBACK_STOP_RATIO // 固定 0.50 const usingFallback = trailingStopPrice < absoluteStopPrice const effectiveStopPrice = usingFallback ? absoluteStopPrice : trailingStopPrice return { effectiveStopPrice, trailingStopPrice, absoluteStopPrice, usingFallback } }

このデュアルトレーリングストップロス設計は、2つの典型的なポジションシナリオに対応する:利益が出ている段階では、トレーリングストップロスラインは価格上昇に伴って上がり、含み益を固定する;エントリー直後に損失が出た場合、トレーリングストップロスラインがマイナスまたはゼロに近づく可能性があり、その時点で絶対的な下限ライン(エントリー価格×50%)が引き継ぎ、ポジションがゼロになるまで下落するのを防ぐ。2つのラインはそれぞれ役割を果たし、どちらも欠かせない。

ストップロスがトリガーされたら成行売却を呼び出し、その銘柄の過去最高価格キャッシュをクリアし、次のオープンシグナルを待つ。ダッシュボードは各ポジションのストップロスラインまでの余裕をリアルタイムで表示し、色分けで警戒度を示し、人手によるレビューを容易にする。


重要な設計判断

なぜマルチロールなのか、単一のプロンプトではないのか? この問題は分析モジュールの部分で一部触れたが、もう少し明確に述べる価値がある。単一プロンプトの問題は「自己説得」だけでなく、より隠れたリスクがある:AIがK線データとニューステキストを同時に処理するとき、後者を使って前者を説明しようとし、K線の異常をニュースの必然的な反映として「合理化」し、両者を独立したシグナルとしてそれぞれ評価しない傾向がある。役割を分離することの本質は、2つの独立した情報チェーンを強制的に導入し、最終的に集約するときに両方のチェーンが同じ方向を指していれば、信頼性は初めて本物になる。

なぜ毎ラウンド最良の1つだけを買い、分散保有しないのか? 分散保有は伝統的な金融において確立された理論的根拠があるが、予測市場ではロジックが完全には当てはまらない。予測市場の銘柄の結果は二元的であり、ゼロになるリスクは現実に存在する。もし同時に3つの中程度の信頼度の銘柄を保有し、そのうち2つがゼロになり1つが上昇しても、全体として元本を回収できない可能性がある。現在の戦略の選択は:正確性に集中して賭け、質の異なるシグナルを数で薄めることをしない、というものである。このトレードオフは必ずしも最適ではないが、ロジック的には自己矛盾がない。

満期日による重み付けルールはなぜハード制約にする必要があるのか? 満期が近づくと、市場価格は参加者のリアルタイムの予想を反映するものであり、これらの参加者の多くはAIの学習データよりも新しい情報を持っている。もしAIが依然として「価格は大幅に割安で、買うべき」と出力する場合、その情報自体が既に時代遅れである可能性が高い。このルールを提案ではなく制約にする理由は、AI自身が「自分の情報はもはや十分に新しい」と認識することは非現実的であり、外部からの強制的な介入が必要だからである。


実運用の観察

実際の稼働記録を見ると、戦略のファネル比率はかなり急峻である。ある時は141銘柄が一次スクリーニングを通過したが、Kライン異常検出後には3銘柄に減少し、最終的に資金シグナル・ニュースサポート・価格評価の3条件全てを満たして注文が発動されたのは1銘柄のみで、残り2銘柄は価格評価が「合理的」または総合スコア不足によりスキップされた。これは戦略全体が保守的であり、基準を下げるよりもポジションを持たないことを選ぶ傾向があることを示している。相場が安定し、市場で明らかなイベントがないウィンドウ期間中は、システムが終日買いシグナルを生成しないこともある。これは設計上の方向性であり、バグではない。また、Kライン異常と判定された後に実際には偽のブレイクアウトであることが判明し、資金が一時的に参入してから引き上げられ、移動ストップが介入してエグジットしたケースもある。ニュース分析にもノイズが存在し、関連イベントの報道自体が遅れていたり、偏りがある可能性があり、AIだけでは完全に識別できない。

ダッシュボードでは、アカウント純資産、保有ポジション状況(現在のBid/Ask、最高値、逆行幅からストップロスラインまでの距離、ストップロスモード)、AI判断の詳細がリアルタイムで表示され、人手による確認を容易にしている。

img


最後に

正直なところ、このシステムはどちらかというと実行可能なアイデアの枠組みであり、箱から出してすぐ使える印鑑機ではない。中核となるロジック――資金行動のトレース、相場とニュースの二重検証、マルチロールAI判断――は概念的には一定の合理性を持つが、実際の市場ではシグナルの品質はまちまちであり、AIの判断も誤ることがあり、期限が近づくにつれて価格が収束するリスクは常に存在する。

改善の余地は多くある:取引銘柄はさらに自分の得意分野に絞り込むことができる。AIのプロンプトは自分の取引スタイルに合わせて再設計できる。5種類のKライン異常の重みや閾値には調整の余地があり、ストップロス係数も実際のポジション運用結果に応じて反復的に改善する価値がある。コードはすべてオープンソースであり、自由に改造してほしい。

リスク注意

予測市場の銘柄はバイナリー結果の特性を持ち、ポジションにはゼロになるリスクがある。Kライン異常シグナルやAI分析の結論はいずれも投資アドバイスを構成するものではなく、過去のシグナル実績が将来の利益を保証するものではない。本戦略は長期間の実運用による検証を受けていない。パラメータ設定が結果に大きな影響を与える。本戦略を実運用で使用する前に、Polymarketのプラットフォームルール、契約満期メカニズム、流動性リスクを十分に理解し、元本損失の許容範囲を自己評価すること。

戦略ソースコード:Polymarket マルチロールインテリジェント分析取引戦略

Comment
All comments (0)
No data
No data
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)