【道楽】株の売買システムを自動的に実行する[3/4]
~ 続き ~
マケスピ2の起動メソッドを作ったので、次はエクセルを動かす話
仕様はシンプルだが・・・・
- エクセルワークブックを開く
- マクロを実行する
- 接続許可、発注許可操作をする
- エクセル終了処理はしない。クローズはVBA側で行う
実際やると面倒の連続だったので愚痴を書いておく
エクセルワークブックを開く
検索すると、Microsoft.Office.Interop.Excel を使うやり方が良く出てくる
だがこれだとマケスピ2のアドインが読み込まれない
あとからロードできるらしいが、そもそもこれを使う理由がないことに気付いた
自動売買の処理は全部VBAなんだから、単にエクセルが起動すればそれでいいのだ
なので process.start() で起動する
マクロを実行する
マクロはショートカットキーで起動する。
なのでエクセルファイル側の設定で、VBAの起動ショートカットキーに ctrl+m を設定しておく必要がある。
ちなみに
クイックアクセスツールバーにマクロ実行ボタンを置いて押す方式も試したが、何かの拍子でボタンが消えたりして挙動不審だからやめた
接続、発注許可操作をする
エクセルを起動しただけでは発注はできない。
RSSに接続して発注を許可するには、マケスピ2アドインのリボンメニューにあるボタンを押す必要がある
このボタンが曲者で・・・・・
まずUI Automationから押せなくてハマった
ボタン名がステータスを兼ねていて、UI要素としてはトグルボタンの分類らしい。ボタンの押下はInvokePatternではなくTogglePatternが必要だった
それから、リボンメニューの操作が一筋縄ではいかず。
誰もがそうするようにリボンメニューは自動で隠す設定にしている
すると
「リボンメニューを出して」「所望のボタンを押す」という連続した操作が必要になる
この間にPCをちょっとでも弄るとリボンメニューがお隠れになってしまうので具合が悪い。
そんなリボンUIの都合は知らんしどーでもいいと思ったのと
自動実行中でもPCは弄りたいし
リボンメニューを常時表示するのは無粋だしで
結局
クイックアクセスツールバーに置いたボタンを押すことにした
エクセルファイル側の設定で、マーケットスピード2の接続ボタンと発注ボタンを右クリックして、クイックアクセスツールバーに追加しておくのが必要
ちなみにマクロ実行ボタンが勝手に消えてる場合でも、コイツは消えないのが不思議なんだぜ・・・・
その他
マクロを実行してから接続/発注許可する順で書いてるけど、特に順序の制約はないです。
ただし、新たなエクセルを開く度に発注が不許可になるのがRSSの仕様なので、必要なエクセルを全部開いてから接続/発注許可するのが良いと思われる。俺のはVBA動かすと順次別のエクセルファイルをオープンしていくので、マクロ実行→ 暫くしてエクセルが全部開いてから接続/発注許可、としてますねー。
とまあ、これでよーやく
マケスピ2とエクセルを動かせるよーになったので
あとは日付とか時刻に合わせて呼び出すだけだぜ😃!!
~ 続く ~
// Copyright 2022 maxsan
// Licensed under the Apache License, Version 2.0
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Automation;
namespace TradeAutomation
{
internal class ExcelLib
{
public enum MacroExecMethod
{
shortcutKey,
quickAccessToolBar
}
public string bookname;
public string macroName = "app";
public MacroExecMethod macroExecMethod = MacroExecMethod.shortcutKey;
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
/// <summary>
/// エクセルを起動する
/// </summary>
/// <returns></returns>
public int Launch()
{
var methodName = MethodBase.GetCurrentMethod().Name;
Disp(methodName, "開始");
var hWnd = GetWindowHandleByName(bookname + " - Excel", 0);
if (hWnd != IntPtr.Zero)
{
Disp(methodName, bookname + " のウィンドウは既にある");
return -1;
}
// エクセル起動
// interopだとaddinが読み込まれないのでprocess.startを使用
// 引数でブック名を渡すとマクロが動かないのでブック名を直指定
try
{
var proc = new Process();
proc.StartInfo.FileName = bookname;
proc.Start();
//proc.WaitForInputIdle();
}
catch (Exception e)
{
Disp(methodName, "エクセル起動で例外が発生");
Console.WriteLine(e.ToString());
return -1;
}
// ウインドウ生成確認・待ち
hWnd = GetWindowHandleByName(bookname + " - Excel", 300);
if (hWnd == IntPtr.Zero)
{
Disp(methodName, "エクセル起動後、ウィンドウが生成されない");
return -1;
}
Disp(methodName, "起動後ウェイト");
Thread.Sleep(30000);
Disp(methodName, "終了");
return 0;
}
/// <summary>
/// エクセルのマクロを実行する
/// </summary>
/// method1:ctrl + m をマクロショートカットとして起動する
/// method2:クイックアクセスツールバーにマクロボタンを追加して押す
/// <returns></returns>
public int StartMacro()
{
UIAutomationLib ui = new UIAutomationLib();
IntPtr hWnd;
InvokePattern ivpat;
var methodName = MethodBase.GetCurrentMethod().Name;
Disp(methodName, "開始");
hWnd = GetWindowHandleByName(bookname + " - Excel", 300);
if (hWnd == IntPtr.Zero)
{
Disp(methodName, bookname + " のウィンドウがない");
return -1;
}
var el_excel = AutomationElement.FromHandle(hWnd);
if (el_excel == null)
{
Disp(methodName, bookname + " のUIA要素がnull");
return -1;
}
switch (macroExecMethod)
{
case MacroExecMethod.shortcutKey:
// method1
// マクロにショートカットキーctrl + mを登録しておくこと
// TODO:sendkeyは不確実なのでリトライが欲しい
Disp(methodName, "マクロショートカットキーを送信");
ui.Keyin(true, el_excel, "^m");
break;
case MacroExecMethod.quickAccessToolBar:
// method2
// クイックアクセスツールバーにマクロボタンを置いておくこと
// 動作が不安定
var el_qatbar = ui.FindElementByNameAndLCType(el_excel,
"クイック アクセス ツール バー", "ツール バー");
if (el_qatbar == null) return -1;
var el = ui.FindElementByNameAndLCType(el_qatbar,
"app", "ボタン");
if (el == null) return -1;
ivpat = (InvokePattern)el.GetCurrentPattern(InvokePattern.Pattern);
ivpat.Invoke();
break;
default: return -1;
}
Disp(methodName, "終了");
return 0;
}
/// <summary>
/// MarketSpeed II RSSに接続し、発注を許可する
/// クイックアクセスツールバーにボタン登録が必要
/// 戻り値がOKになるまで何度もコールする作り
/// </summary>
/// <returns></returns>
public int RssConnectAndEnableOrder()
{
UIAutomationLib ui = new UIAutomationLib();
IntPtr hWnd;
TogglePattern togpat;
int ret = 0;
var methodName = MethodBase.GetCurrentMethod().Name;
Disp(methodName, "開始");
hWnd = GetWindowHandleByName(bookname + " - Excel", 10);
if (hWnd == IntPtr.Zero)
{
Disp(methodName, bookname + " のウィンドウがない");
return -1;
}
var el_excel = AutomationElement.FromHandle(hWnd);
if (el_excel == null)
{
Disp(methodName, bookname + " のUIA要素がnull");
return -1;
}
// クイックアクセスツールバー捜索
var el_qatbar = ui.FindElementByNameAndLCType(el_excel,
"クイック アクセス ツール バー", "ツール バー");
if (el_qatbar == null)
{
Disp(methodName, "クイックアクセスツールバーのUIA要素がnull");
return -1;
}
// MarketSpeedII 未接続ボタン捜索
var el = ui.FindElementByNameAndLCType(el_qatbar,
"接続中\n", "ボタン");
if (el == null)
{
el = ui.FindElementByNameAndLCType(el_qatbar,
"未接続\n", "ボタン");
if (el == null)
{
Disp(methodName, "接続中/未接続ボタンがない");
return -1;
}
// ボタントグル(toggle)
Disp(methodName, "接続ボタン押下");
togpat = (TogglePattern)el.GetCurrentPattern(TogglePattern.Pattern);
togpat.Toggle();
Disp(methodName, "ウェイト");
Thread.Sleep(5000);
ret = -1;
}
// MarketSpeedII 発注不可ボタン捜索
el = ui.FindElementByNameAndLCType(el_qatbar,
"発注可\n", "ボタン");
if (el == null)
{
el = ui.FindElementByNameAndLCType(el_qatbar,
"発注不可\n", "ボタン");
if (el == null)
{
Disp(methodName, "発注可/発注不可ボタンがない");
return -1;
}
// ボタントグル(toggle)
Disp(methodName, "発注ボタン押下");
togpat = (TogglePattern)el.GetCurrentPattern(TogglePattern.Pattern);
togpat.Toggle();
Disp(methodName, "ウェイト");
Thread.Sleep(5000);
ret = -1;
}
Disp(methodName, "終了");
return ret;
}
/// <summary>
/// ウィンドウ名からハンドルを取得する
/// </summary>
/// <param name="name"></param>
/// <param name="waitspecInSecond"></param>
/// <returns></returns>
private IntPtr GetWindowHandleByName(string name, int waitspecInSecond)
{
IntPtr hWnd = IntPtr.Zero;
int i = 0;
while (hWnd == IntPtr.Zero)
{
hWnd = FindWindow(null, name);
if (++i > waitspecInSecond) return hWnd;
Thread.Sleep(1000);
}
return hWnd;
}
/// <summary>
/// コンソール出力(日時つき)
/// </summary>
/// <param name="msg"></param>
private void Disp(string methodName, string msg)
{
#if true
Console.WriteLine(DateTime.Now + " [" + this.GetType().Name
+ "] " + methodName + " : " + msg);
#endif
}
#if false
/// <summary>
/// リボンUIを使う版
/// 味わい深いのでとっておく
/// </summary>
/// <returns></returns>
public int bind_rss_ribbon()
{
UIAutomationLib ui = new UIAutomationLib();
IntPtr hWnd;
TogglePattern togpat;
int i = 0;
hWnd = (IntPtr)0;
while (hWnd == (IntPtr)0)
{
hWnd = FindWindow(null, bookname + " - Excel");
if (hWnd!=(IntPtr)0) break;
Thread.Sleep(1000);
if (i++ > 300) return -1;
}
var el_excel = AutomationElement.FromHandle(hWnd);
// 接続警告ダイアログBox対応
// TODO タイミングによっては(エクセルが先に起動してるとか)ログインして云々のダイアログBOXが出るので
// OKボタン押さないといけない
// MarketSpeedII リボンタブ捜索
var el_ribbontab = ui.FindElementByNameAndLCType(el_excel,
"マーケットスピード II", "タブ アイテム");
// MarketSpeedII リボンタブを選択(リボンを選択・表示させる)してriboon階層を捜索
var selpat = (SelectionItemPattern)el_ribbontab.GetCurrentPattern(SelectionItemPattern.Pattern);
selpat.Select();
Thread.Sleep(3000); // wait for ribbon appear
var el_ms2cc = ui.FindElementByNameAndLCType(el_excel,
"マーケットスピード II", "カスタム コントロール");
// MarketSpeedII 未接続ボタン捜索
var el = ui.FindElementByNameAndLCType(el_ms2cc,
"接続中\n", "ボタン");
if (el == null)
{
el = ui.FindElementByNameAndLCType(el_ms2cc,
"未接続\n", "ボタン");
// ボタントグル(toggle)
// 自動的に隠すになっていると、トグル後リボンは消える
togpat = (TogglePattern)el.GetCurrentPattern(TogglePattern.Pattern);
togpat.Toggle();
Thread.Sleep(5000);
return -1;
}
// MarketSpeedII リボンタブを選択(リボンを選択・表示させる)してriboon階層を捜索
// 一旦画面から消えた要素は再捜査が必要
selpat.Select(); // トグルするとリボンが引っ込んじゃう
Thread.Sleep(3000); // wait for ribbon appear
el_ms2cc = ui.FindElementByNameAndLCType(el_excel,
"マーケットスピード II", "カスタム コントロール");
// MarketSpeedII 発注不可ボタン捜索
el = ui.FindElementByNameAndLCType(el_ms2cc,
"発注可\n", "ボタン");
if (el == null)
{
el = ui.FindElementByNameAndLCType(el_ms2cc,
"発注不可\n", "ボタン");
// ボタントグル(toggle)
// 自動的に隠すになっていると、トグル後リボンは消える
togpat = (TogglePattern)el.GetCurrentPattern(TogglePattern.Pattern);
togpat.Toggle();
Thread.Sleep(5000);
return -1;
}
return 0;
}
#endif
}
}