【道楽】株の売買システムを自動的に実行する[2/4]

2023年9月27日


~ 続き ~

株の微益自動売買システムを作った件

開発環境を用意したのでその続き

マケスピ2の制御クラスを作った。

仕様はシンプルで

  • 起動してログインする
  • 終了する

こんだけ。

マケスピ2の起動時アップデートにも対応している
といっても
単にログインウィンドウが出るまで待つだけ(最大3600秒)
という結果オーライ処理だぜ

それから、

サーバ切断対応はできてない
切れたら切れっぱなし。
2か月半動かして頻度は月に一度あるかないかだったし

こーいう異常系を拾い始めるとキリがなくてめんどくせー

で、放置決定。

~ 続く ~

[2023/9/27追記]

リモートデスクトップで運用したいが未接続時に例外が出るんだけど?

という感じの質問を頂いたのだが、返信先ドメインがエラーになるので回答をここに貼っておく

マケスピ2が動いてるPCをリモートデスクトップ(RDP)で管理したい、ということかと思いますが
当方は試していないのでわかりません、というのが回答になります

が、
それでは何なので自分なら何を試すか書いておきます

推測ですが
RDP接続を切った後は画面ロックと同じような状態になっていて
セキュリティ上の不都合がありUI Automationが例外を吐くのかも知れません

RDPの代わりにVNCを試してはどうでしょうか
VNCで操作すると操作中や操作後にも画面ロックが掛からなかったと思います
ただし相当昔の記憶なので今は違う挙動かもしれませんし
そもそもUI AutomationとVNCの併用がどうなのかなどわかりませんので
上手くいかなければゴメンナサイ、という程度の話ですが・・・・

// 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 MarketSpeed2Lib
    {
        /// <summary>
        /// マーケットスピード2の実行ファイルのある場所へのパス
        /// </summary>
        public string filepath;

        /// <summary>
        /// マーケットスピード2のログインパスワード
        /// </summary>
        public string passwd;

        [DllImport("user32.dll")]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        /// <summary>
        /// マーケットスピード2を起動してログインする
        /// </summary>
        /// <returns></returns>
        public int LaunchAndLogin()
        {
            UIAutomationLib ui = new UIAutomationLib();
            IntPtr hWnd;
            AutomationElement ms2Element;
            AutomationElement el;
            ValuePattern vpat;
            InvokePattern ivpat;
            WindowPattern wpms2;
            Process proc;
            var methodName = MethodBase.GetCurrentMethod().Name;

            Disp(methodName, "開始");

            hWnd = GetWindowHandleByName("MarketSpeed2", 0);
            if (hWnd != IntPtr.Zero)
            {
                Disp(methodName, "MarketSpeed2のウィンドウハンドラが既にある");
                return -1;
            }

            hWnd = GetWindowHandleByName("MARKETSPEED II ログイン", 0);
            if (hWnd == IntPtr.Zero)
            {
                // Market Speed II 起動
                try
                {
                    proc = new Process();
                    proc.StartInfo.FileName = filepath + "MarketSpeed2.exe";
                    proc.StartInfo.WorkingDirectory = filepath;
                    proc.Start();
                    //proc.WaitForInputIdle();
                }
                catch (Exception e)
                {
                    Disp(methodName, "MarketSpeed2の起動で例外が発生");
                    Console.WriteLine(e.ToString());
                    return -1;
                }

                // 楽天接続失敗ダイアログが出るか確認
                hWnd = GetWindowHandleByName("MarketSpeed2", 10);
                if (hWnd != IntPtr.Zero)
                {
                    Disp(methodName, "MarketSpeed2接続失敗ダイアログが出現と推察");
                    return -1;
                }

                // ログインウィンドウハンドル取得
                hWnd = GetWindowHandleByName("MARKETSPEED II ログイン", 3600);
                if (hWnd == IntPtr.Zero)
                {
                    Disp(methodName, "ログインウィンドウが出てこない");
                    return -1;
                }
            }

            ms2Element = AutomationElement.FromHandle(hWnd);

            // パスワード入力
            // パスワード入力テキストボックスの特定
            el = ui.FindElementByNameAndLCType(ms2Element,
                                                            "パスワード", "編集");
            if (el == null)
            {
                Disp(methodName, "パスワード入力ボックスがない");
                return -1;
            }

            // パスワード入力
            vpat = (ValuePattern)el.GetCurrentPattern(ValuePattern.Pattern);
            vpat.SetValue(passwd);
            Thread.Sleep(1000);

            // ログインボタンクリック
            // ログインボタン捜索
            el = ui.FindElementByNameAndLCType(ms2Element,
                                                "ログイン", "ボタン");
            if (el == null)
            {
                Disp(methodName, "ログインボタンがない");
                return -1;
            }

            // ログインボタンクリック(invoke)
            ivpat = (InvokePattern)el.GetCurrentPattern(InvokePattern.Pattern);
            ivpat.Invoke();
            Thread.Sleep(1000);

            // 起動中にネットワーク接続が失われて
            // ネットワーク接続が無いとポップアップが出る
            // OK押下しログインクリックを試行する
            while (GetWindowHandleByName("MARKETSPEED II", 30) != IntPtr.Zero)
            {
                ClickOKonPopupWindow();
                ivpat = (InvokePattern)el.GetCurrentPattern(InvokePattern.Pattern);
                ivpat.Invoke();
                Thread.Sleep(1000);
            }

            // MarketSpeed2(本体)のウィンドウ生成待ち
            if (IntPtr.Zero == GetWindowHandleByName("MarketSpeed2", 300))
            {
                Disp(methodName, "MarketSpeed2本体のウィンドウ生成待ちタイムアウト");
                return -1;
            }

            // ホームは邪魔だから消す
            hWnd = GetWindowHandleByName("ホーム", 30);
            if (hWnd != IntPtr.Zero)
            {
                ms2Element = AutomationElement.FromHandle(hWnd);
                wpms2 = (WindowPattern)ms2Element.GetCurrentPattern(WindowPattern.Pattern);
                wpms2.Close();
            }

            Disp(methodName, "終了");

            return 0;
        }


        /// <summary>
        /// ポップアップを処理する
        /// ポップアップの種別を特定する情報はないので状況推定して処理すること
        /// ウィンドウ名がいろいろ微妙に異なるので注意
        /// サーバー切断通知ポップアップ
        ///     ネット接続が切れるとメインウィンドウが消えてポップアップが出る
        ///     OKボタンを押下するとだいぶ経ってからログイン画面が出てくる
        /// </summary>
        /// <returns></returns>
        public int ClickOKonPopupWindow()
        {
            UIAutomationLib ui = new UIAutomationLib();
            IntPtr hWnd;
            var methodName = MethodBase.GetCurrentMethod().Name;

            Disp(methodName, "開始");

            hWnd = GetWindowHandleByName("MARKETSPEED II", 0);
            if (hWnd == IntPtr.Zero)
            {
                Disp(methodName, "MARKETSPEED IIのウィンドウハンドラがnull");
                return -1;
            }

            var ms2Element = AutomationElement.FromHandle(hWnd);
            if (ms2Element == null)
            {
                Disp(methodName, "MARKETSPEED IIウィンドウのUIA要素がnull");
                return -1;
            }

            // OKボタンクリック
            // OKボタン捜索
            var el = ui.FindElementByNameAndLCType(ms2Element,
                                                "OK", "ボタン");
            if (el == null)
            {
                Disp(methodName, "OKボタンがない");
                return -1;
            }

            // OKボタンクリック(invoke)
            var ivpat = (InvokePattern)el.GetCurrentPattern(InvokePattern.Pattern);
            ivpat.Invoke();

            // ここではウィンドウの状態が不安定なので固定待ち
            Disp(methodName, "タイムアウト待ち");
            Thread.Sleep(1000 * 60);

            Disp(methodName, "終了");

            return 0;
        }

        /// <summary>
        /// マーケットスピード2のメインウィンドウを最小化する
        /// </summary>
        /// <returns></returns>
        public int MinimizeMainWindow()
        {
            var methodName = MethodBase.GetCurrentMethod().Name;

            Disp(methodName, "開始");

            IntPtr hWnd = GetWindowHandleByName("MarketSpeed2", 0);
            if (hWnd == IntPtr.Zero) return -1;

            var el_ms2 = AutomationElement.FromHandle(hWnd);
            if (el_ms2 == null) return -1;

            var wp = (WindowPattern)el_ms2.GetCurrentPattern(WindowPattern.Pattern);
            wp.SetWindowVisualState(WindowVisualState.Minimized);

            Disp(methodName, "終了");

            return 0;
        }

        /// <summary>
        /// マーケットスピード2のメインウィンドウを閉じる
        /// </summary>
        /// <returns></returns>
        public int CloseMainWindow()
        {
            IntPtr hWnd = GetWindowHandleByName("MarketSpeed2", 0);
            var methodName = MethodBase.GetCurrentMethod().Name;

            Disp(methodName, "開始");

            if (hWnd == IntPtr.Zero) return -1;

            var el_ms2 = AutomationElement.FromHandle(hWnd);
            if (el_ms2 == null) return -1;

            var wp = (WindowPattern)el_ms2.GetCurrentPattern(WindowPattern.Pattern);
            wp.Close();

            Disp(methodName, "終了");

            return 0;
        }

        /// <summary>
        /// マーケットスピード2のポップアップ存在確認
        /// </summary>
        /// <returns></returns>
        public int IsExistPopUpWindow()
        {
            var methodName = MethodBase.GetCurrentMethod().Name;

            IntPtr hWnd = GetWindowHandleByName("MARKETSPEED II", 0);
            if (hWnd == IntPtr.Zero)
            {
                return 0;
            }

            Thread.Sleep(1000);

            Disp(methodName, "MARKETSPEED II ウィンドウハンドラが存在(サーバ接続が切れた可能性がある)");
            return -1;
        }

        /// <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
        }
    }
}