001/*
002 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/GoogleFinanceScraper.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1255 $
005 *  $Date: 2018-03-17 20:34:04 -0700 (Sat, 17 Mar 2018) $
006 */
007package ca.bc.webarts.tools;
008
009import java.util.HashMap;
010import java.util.Properties;
011import javax.json.JsonObject;
012import javax.json.JsonArray;
013import javax.json.JsonNumber;
014import javax.json.JsonString;
015
016import ca.bc.webarts.widgets.ResultSetConverter;
017
018import java.text.DecimalFormat;
019
020
021  /**
022    * The Google finance webpage scraper. It is a class that extends the UrlScraper class to wrap/abstract the login and
023    * credentials away and just focusses on the scraping of data from the restricted pages.<br><br>
024    * <a href="https://trading.credentialdirect.com">https://trading.credentialdirect.com</a> is a site requiring login/authentication, so this class provides autoLogin and scraping of sub-pages.<br>
025    * The site login credentials are NOT kept inside the code for this class; they are in a properties file called '<i>.credential</i>' in the '<i>user.home</i>' directory.
026    * <br>The properites that should be in this file:<ul><li>username</li><li>password</li></ul>Maybe the request properties for the website could go in here too.<br>
027    * <b>copyright (c) 2017-2018 Tom B. Gutwin</b>
028    **/
029public class GoogleFinanceScraper extends UrlScraper
030{
031  /**  A holder for this clients System File Separator.  */
032  public final static String SYSTEM_FILE_SEPERATOR = java.io.File.separator;
033
034  /**  A holder for this clients System line termination separator.  */
035  public final static String SYSTEM_LINE_SEPERATOR =
036                                           System.getProperty("line.separator");
037
038  public final static String COL_DELIM = SqlQuery.DEFAULT_COLUMN_DELIMITOR;
039  public static final String QUOTE_SYMBOL_TOKEN = "^%SYMBOL%^";
040  public static final String QUOTE_MARKET_TOKEN = "^%MARKET%^";
041
042  /**  The users home ditrectory.  */
043  public static String USERHOME = System.getProperty("user.home");
044
045  private String googleFinanceFilename_ = USERHOME+SYSTEM_FILE_SEPERATOR+".googleFinance";
046
047  HashMap <String, String> reqProps = new HashMap<String, String>();
048
049  //https://login.google.com/?.src=fpctx&.intl=ca&.lang=en-CA&authMechanism=primary&yid=&done=https%3A%2F%2Fca.google.com%2F&eid=100&as=1&login=tgutwin&crumb=9D8I.8WDkTW
050  String gLoginUrl_plong = "https://login.google.com/account/challenge/password?.src=fpctx&.intl=ca&.lang=en-CA&authMechanism=primary&yid=tgutwin&done=https%3A%2F%2Ffinance.google.com%2F&as=1&login=tgutwin&crumb=1EAn939fa%2Ft&display=login&s=QQ--&sessionIndex=QQ--&acrumb=FBRp8xFN";
051  String gLoginUrl_pshort = "https://login.google.com/?.intl=ca&.lang=en-CA&login=tgutwin&.src=fpctx&done=https%3A%2F%2Ffinance.google.com%2F&prefill=0";  //"https://login.google.com/account/challenge/password?.src=fpctx&.intl=ca&.lang=en-CA&authMechanism=primary&yid=tgutwin&done=https%3A%2F%2Ffinance.google.com%2F&as=1&login=tgutwin";
052  String gLoginUrl = "https://login.google.com/?display=login&login=tgutwin&done=https%3A%2F%2Ffinance.google.com%2F&prefill=1";  //https://login.google.com/?authMechanism=primary&yid=&done=https%3A%2F%2Ffinance.google.com%2F&eid=100&as=1&login=tgutwin&crumb=9D8I.8WDkTW
053  String gScrapePageUrl = "https://finance.google.ca/finance?q=NASDAQ:ITRI&ei=KZITWpDrINihjAHU9JWIAg";
054  static String gScrapeQuoteTokenizedUrl = "https://finance.google.ca/finance?q="+QUOTE_MARKET_TOKEN+":"+QUOTE_SYMBOL_TOKEN;
055
056  String gLoginFormElement = "pure-form pure-form-stacked";  /* id of the form element */
057  String gUserLoginElement = "displayName";
058  String gPasswordElement = "login-passwd";
059  String gUsername = ""; // load from outside somewhere
060  String gPassword = ""; // load from outside somewhere
061
062  String gScrapeQuoteStart= "<div id=market-data-div";
063  String gScrapeQuoteStart_= "<table><tr><td class=\"JgXcPd\">";
064  String gScrapeQuoteEnd= "<script>google.finance.renderMarketData();</script>";
065  String gScrapeQuoteEnd_= "</td></tr></table></div></div></div></g-card-section></div></div></div>";
066
067  private JsonArray holdings = null;
068  private JsonObject dataHome = null;
069
070  protected String quoteStringCache_ = "";
071
072  /**
073    * Default constructor for the Yahoo! Finance webpage scraper.
074    * <a href="https://finance.google.com">https://finance.google.com</a> is a site requiring login; this class wraps the login and
075    * credentials away and just focusses on the scraping of data from the restricted pages.<br><br>
076    *
077    * The constructor ONLY gets the class fields and properties setup to go, BUT does no web page access;
078    * that is left up to the doLogin and   methods.
079    **/
080  public GoogleFinanceScraper( )
081  {
082    super();
083
084    alreadyLoggedIn_ = true;
085    loadProperties();
086
087    reqProps.put("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
088    reqProps.put("Accept-Encoding       ","gzip, deflate, br");
089    reqProps.put("Accept-Language       ","en-US,en;q=0.5");
090    reqProps.put("Connection","keep-alive");
091    reqProps.put("Cookie","_ga=GA1.2-2.2126383985.1485192339; SC=RV=:ED=ca; _ga=GA1.3-2.2126383985.1485192339; _gid=GA1.3-2.1169777638.1511223678; NID=117=XCFBclyBN8yMrHodqAHGAnzZqTHHgdgIC7ZMLZEstpmmhvehgkI0QeHLVkvWLG0uySKvItWNuKphGmBAJe4QzCD6MBGfgLclSeW27xySZ5L_M-iSNTfeVfQHYCkl0tqP15yuhEvq7f24oyMTTyVT_QN9B3y9o8EN1Anhb9JZ; OGPC=5061451-2:5061821-3:; 1P_JAR=2017-11-21-2; OGP=-5061451:-5061821:; __gads=ID=71d1c22d471495fa:T=1508459738:S=ALNI_MbCde9o7niI4fDhlWp2dJCbeAAdNQ; S=quotestreamer=PY9m_pUIUhUQlj-rEAMVepsfJzJoAXRV");
092    reqProps.put("Upgrade-Insecure-Requests","1");
093    reqProps.put("Host","finance.google.ca");
094
095    setLoginUrl(gLoginUrl_pshort);
096    setLoginFormID(gLoginFormElement);
097    setUsernameFormElementName(gUserLoginElement);
098    setPasswordFormElementName(gPasswordElement);
099    setUsername(gUsername);
100    setPassword(gPassword);
101    setScrapePageUrl(gScrapePageUrl);
102    setScrapeStart(gScrapeQuoteStart);
103    setScrapeEnd(gScrapeQuoteEnd);
104    setRequestProps(reqProps);
105  }
106
107
108  /**
109    * Load the private properties from an external/private file.
110    * Site login credentials are NOT kept in this class; they are in a properties file called .credential in the 'user.home' directory.
111    * <br>The properites that should be in this file:<ul><li>username</li><li>password</li></ul>Maybe the request properties for the website could go in here too.
112    *
113    * @return truee if successful, false if could not load from file
114    **/
115  private boolean loadProperties()
116  {
117    boolean retVal = false;
118    try
119    {
120      //get user/pass from ~/.credential
121      Properties gProps = new Properties();
122      gProps.load(new java.io.FileReader(googleFinanceFilename_));
123      gUsername = gProps.getProperty("username");
124      gPassword = gProps.getProperty("password");
125      retVal = true;
126    }
127    catch (Exception ex)
128    {
129      // use defaults or set before use
130      System.out.println("ERROR : did not read props from :"+googleFinanceFilename_);
131    }
132
133    return retVal;
134  }
135
136
137  /**
138    * Not needed because this is a open webpage.
139    **/
140  @Override
141  public  boolean  doLogin()
142  {
143    boolean retVal = alreadyLoggedIn_;
144    reqProps.put("Content-Type","application/x-www-form-urlencoded");
145
146    if(!alreadyLoggedIn_)
147      if(!"".equals(getUsername()) && !"".equals(getPassword()))
148        retVal = super.doLogin();
149      else
150        System.out.println("ERROR reading username and password");
151
152    return retVal;
153  }
154
155
156  /** Get the latest stock QUOTE using old website.
157    *
158    * @param stockSymbol is the Symbol of the stock to lookup
159    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
160    * @return the quote OR 0.0 if not found
161    **/
162  private double getQuoteOld(String stockSymbol, String marketSymbol)
163  {
164
165    double retVal = 0.0;
166    String currPriceStr = null;
167        String result = getQuoteString(stockSymbol, marketSymbol);
168        writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
169        //Looking for
170        // <span class="pr">
171        // <span id="ref_630737340410842_l">
172        //  3.72</span>
173        // </span>
174    try
175    {
176      int offset = "class=\"pr\">".length();
177      int s= result.indexOf("class=\"pr\">");
178      if (s==-1)
179      {
180        s = result.indexOf("class=pr>");
181        currPriceStr = result.substring( s);
182        currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>')+1); // should be from 3.72</span
183        currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('<'));
184        retVal = Double.parseDouble(currPriceStr);
185      }
186      else
187      {
188        currPriceStr = result.substring( s);
189        currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>', offset)+1); // should be from 3.72</span
190        currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('<'));
191        retVal = Double.parseDouble(currPriceStr);
192      }
193    }
194    catch(java.lang.StringIndexOutOfBoundsException oobEx)
195    {
196      System.out.println("ERROR: get quote parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
197      oobEx.printStackTrace();
198      System.out.println(currPriceStr);
199    }
200    return retVal;
201  }
202
203
204  /** Get the latest stock QUOTE from new Website Circa 2018.
205    *
206    * @param stockSymbol is the Symbol of the stock to lookup
207    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
208    * @return the quote OR 0.0 if not found
209    **/
210  private double getQuoteNew(String stockSymbol, String marketSymbol)
211  {
212
213    double retVal = 0.0;
214    String currPriceStr = null;
215        String result = getQuoteString(stockSymbol, marketSymbol);
216        writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
217    try
218    {
219
220        String str = "<div><span><span jsl=\"$t t-cLlm2e1WimE;$x 0;\"";
221        String str2 = "<span class=\"IsqQVc NprOob inM7_hGITiio-zJFzKq8ukm8\">";  // needed if there is a 2nd option
222        String str3 = "><span class=\"";
223        int offset = str.length();
224        int s= result.indexOf(str);
225        if (s==-1)
226        {
227          offset = str2.length();
228          s = result.indexOf(str2);
229          if (s!=-1)
230          {
231            currPriceStr = result.substring( s+offset);
232            int offset2 = str3.length();
233            int s2= currPriceStr.indexOf(str3);
234            currPriceStr = currPriceStr.substring( s2+offset2);
235            int s3= currPriceStr.indexOf("\">");
236            currPriceStr = currPriceStr.substring( s3+2);
237            currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</span>"));
238            retVal = Double.parseDouble(currPriceStr.replace(",",""));
239          }
240        }
241        else
242        {
243          currPriceStr = result.substring( s+offset);
244          int offset2 = str3.length();
245          int s2= currPriceStr.indexOf(str3);
246          currPriceStr = currPriceStr.substring( s2+offset2);
247          int s3= currPriceStr.indexOf("\">");
248          currPriceStr = currPriceStr.substring( s3+2);
249          currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</span>"));
250          retVal = Double.parseDouble(currPriceStr.replace(",",""));
251        }
252    }
253    catch(java.lang.StringIndexOutOfBoundsException oobEx)
254    {
255      System.out.println("ERROR: get quote parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
256      oobEx.printStackTrace();
257      System.out.println(currPriceStr);
258    }
259    return retVal;
260  }
261
262
263    /** Get the latest stock QUOTE .
264    *
265    * @param stockSymbol is the Symbol of the stock to lookup
266    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
267    * @return the quote OR 0.0 if not found
268    **/
269  public double getQuote(String stockSymbol, String marketSymbol)
270  {
271    return getQuoteNew(stockSymbol, marketSymbol);
272  }
273
274
275  public String getTodaysTSXChartAndTableHtmlStr()
276  {
277    return  getTodaysTSXChartAndTableHtmlStr(true);
278  }
279
280
281  /** Get the days TSX Index chart and data .
282    *
283    * @return html containing todays TSX indices chart
284    **/
285  public String getTodaysTSXChartAndTableHtmlStr(boolean includeTable)
286  {
287    String retVal = "";
288    boolean success = false;
289    boolean doLogin = false;
290    setScrapePageUrl("https://finance.google.ca/finance");
291    setScrapeStart("<div class=id-summary-chart>");
292    setScrapeEnd("</div></div><div class=\"sfe-section clf\">");
293    //setScrapeStart("BlahBlahBlah");
294    //setScrapeEnd("BlahBlahBlah");
295
296    HashMap <String, String> reqProps = null;
297
298    setDebugOut();
299
300    if(doLogin)
301    {
302      if(!"".equals(getUsername()) && !"".equals(getPassword()))
303      {
304        success = doLogin();
305        //System.out.println("\nLogin Response:\n"+postPageResponse_);
306      }
307      else
308        System.out.println("ERROR reading username and password");
309    }
310    else
311      success = true;
312
313    if(success)
314    {
315      String result =  doScrape(false);
316      retVal = result;
317
318      //System.out.println("Scraped Chart:\n"+retVal);
319    }
320
321    /*
322      //Looking for
323      // <span class="pr">
324      // <span id="ref_630737340410842_l">
325      //  3.72</span>
326      // </span>
327      try
328      {
329        int offset = "Vol / Avg.</td>".length();
330        int s= result.indexOf("Vol / Avg.</td>");
331        if (s==-1)
332        {
333          s = result.indexOf("class=pr>");
334          if (s!=-1)
335          {
336            currPriceStr = result.substring( s);
337            currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>')+1); // should be from 3.72</span
338            if(currPriceStr.indexOf('M')!=-1 && currPriceStr.indexOf('M')<8)
339            {
340              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('M'));
341              retVal = Double.parseDouble(currPriceStr.replace(",",""))*1000000.0;
342            }
343            else
344            {
345              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('/'));
346              retVal = Double.parseDouble(currPriceStr.replace(",",""));
347            }
348          }
349        }
350        else
351        {
352          currPriceStr = result.substring( s);
353          //System.out.println("\nDEBUG:"+currPriceStr);
354          currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>', offset)+1); // should be from 3.72</span
355          //System.out.println("\nDEBUG:"+currPriceStr);
356            if(currPriceStr.indexOf('M')!=-1 && currPriceStr.indexOf('M')<8)
357            {
358              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('M'));
359              retVal = Double.parseDouble(currPriceStr.replace(",",""))*1000000.0;
360            }
361            else
362            {
363              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('/'));
364              retVal = Double.parseDouble(currPriceStr.replace(",",""));
365            }
366        }
367      }
368      catch(java.lang.StringIndexOutOfBoundsException oobEx)
369      {
370        System.out.println("ERROR: get volume parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
371        oobEx.printStackTrace();
372        System.out.println(currPriceStr);
373      }
374    */
375    return retVal;
376  }
377
378
379    /** Get the latest stock stat for the passed in valueName from the NEW GFinance pages Circa 2018.
380    *
381    * @param stockSymbol is the Symbol of the stock to lookup
382    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
383    * @param valueName is the stat to scrape - Open, Close, High, Low, 52-wk high, 52-wk low, Div yield
384    * @return the price OR -1 if not found;
385    **/
386  private double scrapeValueNew(String stockSymbol, String marketSymbol, String valueName)
387  {
388    double retVal = -1.0;
389    String currPriceStr = null;
390    if ("Close".equalsIgnoreCase(valueName)) valueName = "Prev close";
391    if ("Prev close".equalsIgnoreCase(valueName)) valueName = "Prev close";
392    if ("Div".equalsIgnoreCase(valueName)) valueName = "Div yield";
393    if ("Dividend".equalsIgnoreCase(valueName)) valueName = "Div yield";
394    if ("Div yield".equalsIgnoreCase(valueName)) valueName = "Div yield";
395    if(!"".equalsIgnoreCase(marketSymbol))
396    {
397      retVal = 0.0;
398      String result = getQuoteString(stockSymbol, marketSymbol);
399      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
400      //Looking for
401      // <div class="ZSM8k"><table><tr>
402      //   <td class="JgXcPd">Open</td><td class="iyjjgb">21.50</td></tr>
403      //   <tr><td class="JgXcPd">High</td><td class="iyjjgb">21.93</td></tr>
404      //   <tr><td class="JgXcPd">Low</td><td class="iyjjgb">20.65</td></tr>
405      //   <tr><td class="JgXcPd">Mkt cap</td><td class="iyjjgb">567.04M</td></tr>
406      //   <tr><td class="JgXcPd">P/E ratio</td><td class="iyjjgb">10.42</td></tr>
407      //   </table></div>
408      //   <div class="ZSM8k"><table><tr>
409      //     <td class="JgXcPd">Div yield</td><td class="iyjjgb">1.94%</td></tr>
410      //     <tr><td class="JgXcPd">Prev close</td><td class="iyjjgb">20.87</td></tr>
411      //     <tr><td class="JgXcPd">52-wk high</td><td class="iyjjgb">26.11</td></tr>
412      //     <tr><td class="JgXcPd">52-wk low</td><td class="iyjjgb">17.46</td></tr>
413      // </table></div>
414      try
415      {
416        String str = "<td class=\"JgXcPd\">"+valueName+"</td><td class=\"iyjjgb\">";
417        String str2 = "<td class=JgXcPd>"+valueName+"</td><td class=iyjjgb>";
418        int offset = str.length();
419        int s= result.indexOf(str);
420        if (s==-1)
421        {
422          offset = str2.length();
423          s = result.indexOf(str2);
424          if (s!=-1)
425          {
426            currPriceStr = result.substring( s+offset);
427            if(valueName == "Div yield")
428            {
429              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("%</"));
430              if(currPriceStr.startsWith("-")) currPriceStr = "0.0";
431            }
432            else
433              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</"));
434            retVal = Double.parseDouble(currPriceStr.replace(",",""));
435          }
436        }
437        else
438        {
439            currPriceStr = result.substring( s+offset);
440            if(valueName == "Div yield")
441            {
442              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("%</"));
443              if(currPriceStr.startsWith("-")) currPriceStr = "0.0";
444            }
445            else
446              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</"));
447            retVal = Double.parseDouble(currPriceStr.replace(",",""));
448        }
449      }
450      catch(java.lang.StringIndexOutOfBoundsException oobEx)
451      {
452        System.out.println("ERROR: scraping "+valueName+". See result html in file "+ "gQuote-"+stockSymbol+".txt");
453        oobEx.printStackTrace();
454        System.out.println(currPriceStr);
455      }
456      catch(java.lang.NumberFormatException mfEx)
457      {
458        System.out.println("ERROR: number Format error scraping "+valueName+". See result html in file "+ "gQuote-"+stockSymbol+".txt");
459        mfEx.printStackTrace();
460        System.out.println(currPriceStr);
461      }
462    }
463    return retVal;
464  }
465
466
467  /** Get the latest stock Open price from the OLD GFinance Pages .
468    *
469    * @param stockSymbol is the Symbol of the stock to lookup
470    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
471    * @return the price OR -1 if not found;
472    **/
473  private double getOpenOld(String stockSymbol, String marketSymbol)
474  {
475
476    double retVal = -1.0;
477    String currPriceStr = null;
478    if(!"".equalsIgnoreCase(marketSymbol))
479    {
480      retVal = 0.0;
481      String result = getQuoteString(stockSymbol, marketSymbol);
482      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
483      //Looking for
484      // <span class="pr">
485      // <span id="ref_630737340410842_l">
486      //  3.72</span>
487      // </span>
488      try
489      {
490        int offset = "Open</td><td class=\"val\">".length();
491        int s= result.indexOf("Open</td><td class=\"val\">");
492        if (s==-1)
493        {
494          offset = "Open</td><td class=val>".length();
495          s = result.indexOf("Open</td><td class=val>");
496          if (s!=-1)
497          {
498            currPriceStr = result.substring( s+offset);
499            currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</"));
500            retVal = Double.parseDouble(currPriceStr.replace(",",""));
501          }
502        }
503        else
504        {
505          currPriceStr = result.substring( s+offset);
506          currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf("</"));
507          retVal = Double.parseDouble(currPriceStr.replace(",",""));
508        }
509      }
510      catch(java.lang.StringIndexOutOfBoundsException oobEx)
511      {
512        System.out.println("ERROR: get open parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
513        oobEx.printStackTrace();
514        System.out.println(currPriceStr);
515      }
516    }
517    return retVal;
518  }
519
520
521  /** Get the latest stock Open price from the NEW GFinance pages Circa 2018.
522    *
523    * @param stockSymbol is the Symbol of the stock to lookup
524    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
525    * @return the price OR -1 if not found;
526    **/
527  private double getOpenNew(String stockSymbol, String marketSymbol)
528  {
529    return scrapeValueNew(stockSymbol, marketSymbol, "Open");
530  }
531
532
533    /** Get the latest stock Open price.
534    *
535    * @param stockSymbol is the Symbol of the stock to lookup
536    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
537    * @return the price OR -1 if not found;
538    **/
539  public double getOpen(String stockSymbol, String marketSymbol)
540  {
541    return getOpenNew(stockSymbol, marketSymbol);
542  }
543
544
545  /** Get the latest stock Days High price  from the Old GFinance pages before  2018.
546    *
547    * @param stockSymbol is the Symbol of the stock to lookup
548    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
549    * @return the price OR -1 if not found;
550    **/
551  private double getDaysHighOld(String stockSymbol, String marketSymbol)
552  {
553
554    double retVal = -1.0;
555    String currLowStr = null;
556    String currHighStr = null;
557    if(!"".equalsIgnoreCase(marketSymbol))
558    {
559      retVal = 0.0;
560      String result = getQuoteString(stockSymbol, marketSymbol);
561      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
562      //Looking for
563      // Range</td><td class="val">
564      try
565      {
566        int offset = "Range</td><td class=\"val\">".length();
567        int s= result.indexOf("Range</td><td class=\"val\">");
568        if (s==-1)
569        {
570          offset = "Range</td><td class=val>".length();
571          s = result.indexOf("Range</td><td class=val>");
572          if (s!=-1)
573          {
574            currLowStr = result.substring( s+offset);
575            currHighStr = currLowStr.substring( currLowStr.indexOf(" - ")+3);
576            currHighStr = currHighStr.substring( 0, currHighStr.indexOf("</td>"));
577            currLowStr = currLowStr.substring( 0, currLowStr.indexOf(" - "));
578            retVal = Double.parseDouble(currHighStr.replace(",",""));
579          }
580        }
581        else
582        {
583            currLowStr = result.substring( s+offset);
584            currHighStr = currLowStr.substring( currLowStr.indexOf(" - ")+3);
585            currHighStr = currHighStr.substring( 0, currHighStr.indexOf("</td>"));
586            currLowStr = currLowStr.substring( 0, currLowStr.indexOf(" - "));
587            retVal = Double.parseDouble(currHighStr.replace(",",""));
588        }
589      }
590      catch(java.lang.StringIndexOutOfBoundsException oobEx)
591      {
592        System.out.println("ERROR: get days high parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
593        oobEx.printStackTrace();
594        System.out.println(currHighStr);
595      }
596    }
597    return retVal;
598  }
599
600
601  /** Get the latest stock Days High price  from the NEW GFinance pages Circa 2018.
602    *
603    * @param stockSymbol is the Symbol of the stock to lookup
604    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
605    * @return the price OR -1 if not found;
606    **/
607  private double getDaysHighNew(String stockSymbol, String marketSymbol)
608  {
609    return scrapeValueNew(stockSymbol, marketSymbol, "High");
610  }
611
612
613  /** Get the latest stock Days High price .
614    *
615    * @param stockSymbol is the Symbol of the stock to lookup
616    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
617    * @return the price OR -1 if not found;
618    **/
619  public double getDaysHigh(String stockSymbol, String marketSymbol)
620  {return getDaysHighNew( stockSymbol,  marketSymbol);}
621
622
623  /** Get the latest stock Days Low price .
624    *
625    * @param stockSymbol is the Symbol of the stock to lookup
626    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
627    * @return the price OR -1 if not found;
628    **/
629  private double getDaysLowOld(String stockSymbol, String marketSymbol)
630  {
631
632    double retVal = -1.0;
633    String currLowStr = null;
634    String currHighStr = null;
635    if(!"".equalsIgnoreCase(marketSymbol))
636    {
637      retVal = 0.0;
638      String result = getQuoteString(stockSymbol, marketSymbol);
639      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
640      //Looking for
641      // Range</td><td class="val">
642      try
643      {
644        int offset = "Range</td><td class=\"val\">".length();
645        int s= result.indexOf("Range</td><td class=\"val\">");
646        if (s==-1)
647        {
648          offset = "Range</td><td class=val>".length();
649          s = result.indexOf("Range</td><td class=val>");
650          if (s!=-1)
651          {
652            currLowStr = result.substring( s+offset);
653            currHighStr = currLowStr.substring( currLowStr.indexOf(" - ")+3);
654            currHighStr = currHighStr.substring( 0, currHighStr.indexOf("</td>"));
655            currLowStr = currLowStr.substring( 0, currLowStr.indexOf(" - "));
656            retVal = Double.parseDouble(currLowStr.replace(",",""));
657          }
658        }
659        else
660        {
661            currLowStr = result.substring( s+offset);
662            currHighStr = currLowStr.substring( currLowStr.indexOf(" - ")+3);
663            currHighStr = currHighStr.substring( 0, currHighStr.indexOf("</td>"));
664            currLowStr = currLowStr.substring( 0, currLowStr.indexOf(" - "));
665            retVal = Double.parseDouble(currLowStr.replace(",",""));
666        }
667      }
668      catch(java.lang.StringIndexOutOfBoundsException oobEx)
669      {
670        System.out.println("ERROR: get days high parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
671        oobEx.printStackTrace();
672        System.out.println(currLowStr);
673      }
674    }
675    return retVal;
676  }
677
678
679  /** Get the latest stock Days Low price  from the NEW GFinance pages Circa 2018.
680    *
681    * @param stockSymbol is the Symbol of the stock to lookup
682    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
683    * @return the price OR -1 if not found;
684    **/
685  private double getDaysLowNew(String stockSymbol, String marketSymbol)
686  {
687    return scrapeValueNew(stockSymbol, marketSymbol, "Low");
688  }
689
690
691  /** Get the latest stock Days Low price .
692    *
693    * @param stockSymbol is the Symbol of the stock to lookup
694    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
695    * @return the price OR -1 if not found;
696    **/
697  public double getDaysLow(String stockSymbol, String marketSymbol)
698  {return getDaysLowNew( stockSymbol,  marketSymbol);}
699
700
701  /** Get the latest stock Dividend Yield % .
702    * Latest dividend/dividend yield<br><br>
703    * Latest dividend is dividend per share paid to shareholders in the most recent quarter.<br>
704    * Dividend yield is the value of the latest dividend, multiplied by the number of times dividends are typically paid per year, divided by the stock price.
705    *
706    * @param stockSymbol is the Symbol of the stock to lookup
707    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
708    * @return the % OR -1 if not found;
709    **/
710  private double getLatestDividendOld(String stockSymbol, String marketSymbol)
711  {
712
713    double retVal = -1.0;
714    String findStr = "Div/yield</td><td class=\"val\">";
715    String findStr2 = "Div/yield</td><td class=val>";
716    String dividendStr = null;
717    String divYieldStr = null;
718    if(!"".equalsIgnoreCase(marketSymbol))
719    {
720      retVal = 0.0;
721      String result = getQuoteString(stockSymbol, marketSymbol);
722      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
723      //Looking for
724      // Range</td><td class="val">
725      try
726      {
727        int offset = findStr.length();
728        int s= result.indexOf(findStr);
729        if (s==-1)
730        {
731          offset = findStr2.length();
732          s = result.indexOf(findStr2);
733          if (s!=-1)
734          {
735            dividendStr = result.substring( s+offset);
736            if(!dividendStr.startsWith("&"))
737            {
738              divYieldStr = dividendStr.substring( dividendStr.indexOf("/")+1);
739              divYieldStr = divYieldStr.substring( 0, divYieldStr.indexOf("</td>"));
740              dividendStr = dividendStr.substring( 0, dividendStr.indexOf("/"));
741              retVal = Double.parseDouble(dividendStr.replace(",",""));
742            }
743          }
744        }
745        else
746        {
747          dividendStr = result.substring( s+offset);
748          if(!dividendStr.startsWith("&"))
749          {
750            divYieldStr = dividendStr.substring( dividendStr.indexOf("/")+1);
751            divYieldStr = divYieldStr.substring( 0, divYieldStr.indexOf("</td>"));
752            dividendStr = dividendStr.substring( 0, dividendStr.indexOf("/"));
753            retVal = Double.parseDouble(dividendStr.replace(",",""));
754          }
755        }
756      }
757      catch(java.lang.StringIndexOutOfBoundsException oobEx)
758      {
759        System.out.println("ERROR: get  dividend parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
760        oobEx.printStackTrace();
761        System.out.println(dividendStr);
762      }
763    }
764    return retVal;
765  }
766
767
768  /** Get the latest stock Div Yield %  from the NEW GFinance pages Circa 2018.
769    *
770    * @param stockSymbol is the Symbol of the stock to lookup
771    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
772    * @return the div % OR -1 if not found;
773    **/
774  private double getLatestDividendNew(String stockSymbol, String marketSymbol)
775  {
776    return scrapeValueNew(stockSymbol, marketSymbol, "Div yield");
777  }
778
779
780  /** Get the latest stock Div Yield %.
781    *
782    * @param stockSymbol is the Symbol of the stock to lookup
783    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
784    * @return the div % OR -1 if not found;
785    **/
786  public double getLatestDividend(String stockSymbol, String marketSymbol)
787  {return getLatestDividendNew( stockSymbol,  marketSymbol);}
788
789
790  /** Get the latest stock Volume .
791    *
792    * @param stockSymbol is the Symbol of the stock to lookup
793    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
794    * @return the most recent daily volume OR -1 if not found;
795    **/
796  private double getVolumeOld(String stockSymbol, String marketSymbol)
797  {
798
799    double retVal = -1.0;
800    String currPriceStr = null;
801    if(!"MUTF_CA".equalsIgnoreCase(marketSymbol))
802    {
803      retVal = 0.0;
804      String result = getQuoteString(stockSymbol, marketSymbol);
805      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
806      //Looking for
807      // <span class="pr">
808      // <span id="ref_630737340410842_l">
809      //  3.72</span>
810      // </span>
811      try
812      {
813        int offset = "Vol / Avg.</td>".length();
814        int s= result.indexOf("Vol / Avg.</td>");
815        if (s==-1)
816        {
817          s = result.indexOf("class=pr>");
818          if (s!=-1)
819          {
820            currPriceStr = result.substring( s);
821            currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>')+1); // should be from 3.72</span
822            if(currPriceStr.indexOf('M')!=-1 && currPriceStr.indexOf('M')<8)
823            {
824              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('M'));
825              retVal = Double.parseDouble(currPriceStr.replace(",",""))*1000000.0;
826            }
827            else
828            {
829              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('/'));
830              retVal = Double.parseDouble(currPriceStr.replace(",",""));
831            }
832          }
833        }
834        else
835        {
836          currPriceStr = result.substring( s);
837          //System.out.println("\nDEBUG:"+currPriceStr);
838          currPriceStr = currPriceStr.substring( currPriceStr.indexOf('>', offset)+1); // should be from 3.72</span
839          //System.out.println("\nDEBUG:"+currPriceStr);
840            if(currPriceStr.indexOf('M')!=-1 && currPriceStr.indexOf('M')<8)
841            {
842              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('M'));
843              retVal = Double.parseDouble(currPriceStr.replace(",",""))*1000000.0;
844            }
845            else
846            {
847              currPriceStr = currPriceStr.substring( 0, currPriceStr.indexOf('/'));
848              retVal = Double.parseDouble(currPriceStr.replace(",",""));
849            }
850        }
851      }
852      catch(java.lang.StringIndexOutOfBoundsException oobEx)
853      {
854        System.out.println("ERROR: get volume parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
855        oobEx.printStackTrace();
856        System.out.println(currPriceStr);
857      }
858    }
859    return retVal;
860  }
861
862
863  /** Get the latest stock Volume from the NEW GFinance pages Circa 2018 - IF IT EXISTED.
864    *
865    * @param stockSymbol is the Symbol of the stock to lookup
866    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
867    * @return the Volume from most resent day -1 if not found;
868    **/
869  private double getVolumeNew(String stockSymbol, String marketSymbol)
870  {
871    return -1.0; // does not exist in new Google... scrapeValueNew(stockSymbol, marketSymbol, "Div yield");
872  }
873
874
875  /** Get the latest stock Volume.
876    *
877    * @param stockSymbol is the Symbol of the stock to lookup
878    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
879    * @return the div % OR -1 if not found;
880    **/
881  public double getVolume(String stockSymbol, String marketSymbol)
882  {return getVolumeNew( stockSymbol,  marketSymbol);}
883
884
885  /** Get the latest stock Dividend Yield.
886    * Latest dividend/dividend yield<br><br>
887    * Latest dividend is dividend per share paid to shareholders in the most recent quarter.<br>
888    * Dividend yield is the value of the latest dividend, multiplied by the number of times dividends are typically paid per year, divided by the stock price.
889    *
890    * @param stockSymbol is the Symbol of the stock to lookup
891    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
892    * @return the price OR -1 if not found;
893    **/
894  public double getDividendYieldOld(String stockSymbol, String marketSymbol)
895  {
896
897    double retVal = -1.0;
898    String findStr = "Div/yield</td><td class=\"val\">";
899    String findStr2 = "Div/yield</td><td class=val>";
900    String dividendStr = null;
901    String divYieldStr = null;
902    if(!"".equalsIgnoreCase(marketSymbol))
903    {
904      retVal = 0.0;
905      String result = getQuoteString(stockSymbol, marketSymbol);
906      writeStringToFile(result,"gQuote-"+stockSymbol+".txt");
907      //Looking for
908      // Range</td><td class="val">
909      try
910      {
911        int offset = findStr.length();
912        int s= result.indexOf(findStr);
913        if (s==-1)
914        {
915          offset = findStr2.length();
916          s = result.indexOf(findStr2);
917          if (s!=-1)
918          {
919            dividendStr = result.substring( s+offset);
920            if(!dividendStr.startsWith("&"))
921            {
922              divYieldStr = dividendStr.substring( dividendStr.indexOf("/")+1);
923              divYieldStr = divYieldStr.substring( 0, divYieldStr.indexOf("</td>"));
924              dividendStr = dividendStr.substring( 0, dividendStr.indexOf("/"));
925              retVal = Double.parseDouble(divYieldStr.replace(",",""));
926            }
927          }
928        }
929        else
930        {
931          dividendStr = result.substring( s+offset);
932          if(!dividendStr.startsWith("&"))
933          {
934            divYieldStr = dividendStr.substring( dividendStr.indexOf("/")+1);
935            divYieldStr = divYieldStr.substring( 0, divYieldStr.indexOf("</td>"));
936            dividendStr = dividendStr.substring( 0, dividendStr.indexOf("/"));
937            retVal = Double.parseDouble(divYieldStr.replace(",",""));
938          }
939        }
940      }
941      catch(java.lang.StringIndexOutOfBoundsException oobEx)
942      {
943        System.out.println("ERROR: get dividend Yield parse error. See result html in file "+ "gQuote-"+stockSymbol+".txt");
944        oobEx.printStackTrace();
945        System.out.println(divYieldStr);
946      }
947    }
948    return retVal;
949  }
950
951
952  /**
953    * Returns the URL string to the Google page for the specified stock.
954  **/
955  public static String getStockPageUrlStr(String stockSymbol, String marketSymbol)
956  {
957    String retVal = "";
958    retVal = gScrapeQuoteTokenizedUrl.replace(QUOTE_SYMBOL_TOKEN, stockSymbol);
959    retVal = retVal.replace(QUOTE_MARKET_TOKEN, googleMarketSymbol(marketSymbol));
960
961    return retVal;
962  }
963
964
965  /** Requests, scrapes and creates the stock QUOTE data string for the given stock symbol for today.
966    * this data gets used as the data load into the InvestmentTrackerQuery database.<br>
967    * It returns a multi-line result-set string with the first line as the column headings and the remaining lies of data<br>
968    *  open,  daysLow,  daysHigh, previousClose,  daysVolume, dividendPerShare,   dividendPayDate<br>
969    *  90.01, 89.13, 90.01, 90.34, 386147, 4.18, "10/27/2017"
970    *
971    * @param stockSymbol is the Symbol of the stock to lookup
972    * @param marketSymbol is the stock market symbol that Google uses in the quoteUrl
973    * @return the delimited  resultSet OR '' if not found; the resultset is a comma delimited open,  daysLow,  daysHigh, previousClose,  daysVolume, dividendPerShare,   dividendPayDate OR '' if not found
974    **/
975  private String getQuoteString(String stockSymbol, String marketSymbol)  { return getQuoteString(stockSymbol, marketSymbol, true);}
976  private String getQuoteString(String stockSymbol, String marketSymbol, boolean useCache)
977  {
978    String retVal = "";
979    boolean success = false;
980    boolean useDataTool = false;
981    //NASDAQ:FCEL   CVE:HIVE   MUTF_CA:AGF201 TSE:LUN
982
983    if(useCache && stockSymbolCache_ == stockSymbol && stockSymbolCache_ == stockSymbol)
984    {
985      retVal = quoteStringCache_;
986    }
987    else
988    {
989      String tokenReplacedScrapeUrl = gScrapeQuoteTokenizedUrl.replace(QUOTE_SYMBOL_TOKEN,stockSymbol);
990      tokenReplacedScrapeUrl = tokenReplacedScrapeUrl.replace(QUOTE_MARKET_TOKEN,marketSymbol);
991      setScrapePageUrl(tokenReplacedScrapeUrl);
992
993      HashMap <String, String> reqProps = null;
994
995      if(holdings==null && !marketSymbol.equals("MUTF_CA")) // Google is banning my MUTF_CA queries
996      {
997        if(!"".equals(getUsername()) && !"".equals(getPassword()))
998        {
999          success = doLogin();
1000          //System.out.println("\nLogin Response:\n"+postPageResponse_);
1001        }
1002        else
1003          System.out.println("ERROR reading username and password");
1004
1005        if(success)
1006        {
1007          String result = doScrape(reqProps, true); // without any further trimming, only the start/end trimming
1008          retVal = result;
1009        }
1010        stockSymbolCache_ = stockSymbol;
1011        marketSymbolCache_ = marketSymbol;
1012        quoteStringCache_ = retVal;
1013      }
1014    }
1015    return retVal;
1016  }
1017
1018
1019
1020  /** Create the STOCK_DAILY data string  for the given stock symbol for today.
1021    * this data gets used as the data load into the InvestmentTrackerQuery database.<br>
1022    * It returns a multi-line result-set string with the first line as the column headings and the remaining lies of data<br>
1023    *  open,  daysLow,  daysHigh, close, previousClose,  daysVolume, date, dividendPerShare,   dividendPayDate<br>
1024    *  90.01, 89.13, 90.01, 90.00, 90.34, 386147, "11/08/2017", 4.18, "10/27/2017"
1025    *
1026    * @param stockSymbol is the Symbol of the stock to lookup    *
1027    * @return the delimited  resultSet OR '' if not found; the resultset is a comma delimited open,  daysLow,  daysHigh, previousClose,  daysVolume, dividendPerShare,   dividendPayDate OR '' if not found
1028    **/
1029  public String getStockDailyString(String stockSymbol)
1030  {
1031    String retVal = "";
1032    boolean success = false;
1033    JsonObject dataHome = null;
1034    if(holdings==null)
1035    {
1036      if(!"".equals(getUsername()) && !"".equals(getPassword()))
1037        success = doLogin();
1038      else
1039        System.out.println("ERROR reading username and password");
1040
1041      if(success)
1042      {
1043        // scape the summary first to get a list of holdings
1044        String result = doScrape();
1045        JsonObject jsO = toJsonObject(result);
1046        JsonArray jsA = jsO.getJsonArray("Data");
1047        dataHome = jsA.getJsonObject(0);
1048        holdings = dataHome.getJsonArray("Holdings");
1049      }
1050    }
1051    else
1052      System.out.println("Already Logged in, using existing holdings["+holdings.size()+"]");
1053
1054    if(dataHome!=null && holdings!=null)
1055    {
1056      retVal+="\"Open\"";
1057      retVal+=COL_DELIM;
1058      retVal+="\"daysLow\"";
1059      retVal+=COL_DELIM;
1060      retVal+="\"daysHigh\"";
1061      retVal+=COL_DELIM;
1062      retVal+="\"close\"";
1063      retVal+=COL_DELIM;
1064      retVal+="\"previousClose\"";
1065      retVal+=COL_DELIM;
1066      retVal+="\"date\"";
1067      retVal+=COL_DELIM;
1068      retVal+="\"dividendPerShare\"";
1069      retVal+=COL_DELIM;
1070      retVal+="\"dividendPayDate\"";
1071      retVal+="\n";
1072
1073      setScrapeStart(gScrapeQuoteStart);
1074      setScrapeEnd(gScrapeQuoteEnd);
1075      String currSymbol = "";
1076      String currPrefix = "";
1077      JsonObject stock =null;
1078
1079      for(int i=0; i<holdings.size();i++)
1080      {
1081        stock = holdings.getJsonObject(i);
1082        if(stock!=null)
1083        {
1084          currSymbol = stock.getJsonString("Symbol").toString().substring(1,stock.getJsonString("Symbol").toString().length()-1);
1085          currPrefix = stock.getJsonString("CountryPrefix").toString().substring(1,stock.getJsonString("CountryPrefix").toString().length()-1);
1086
1087          if(("\""+stockSymbol+"\"").equalsIgnoreCase(currSymbol.toString()))
1088          {
1089
1090            //get the daily quote for each individual holding
1091            setScrapePageUrl(gScrapeQuoteTokenizedUrl.replace(QUOTE_SYMBOL_TOKEN,currPrefix+currSymbol));
1092            String result = doScrape();
1093            System.out.println(" DEBUG Stock Quote Result \n----------------------------\n");
1094            System.out.println(result);
1095
1096            /*
1097            JsonObject jsO = toJsonObject(result);
1098            JsonString currSymbol = stock.getJsonString("Symbol");
1099            if(("\""+stockSymbol+"\"").equalsIgnoreCase(currSymbol.toString()))
1100            {
1101              JsonNumber  open,  daysLow,  daysHigh, previousClose,  daysVolume, dividendPerShare;
1102              String   dividendPayDate;
1103              //open=stock.getJsonString("Price");
1104              retVal+="\""+dataHome.getString("FriendlyName")+"\"";
1105              retVal+=COL_DELIM;
1106              retVal+=""+dataHome.getJsonString("Currency");
1107              retVal+=COL_DELIM;
1108
1109            }
1110            */
1111            i=holdings.size();
1112          }
1113        }
1114      }
1115    }
1116
1117    return retVal;
1118  }
1119
1120
1121  public static String googleMarketSymbol(String mkt)
1122  {
1123    String retVal = mkt;
1124
1125    if("TSXV".equalsIgnoreCase(mkt)) retVal = "CVE";
1126    else if("TSX".equalsIgnoreCase(mkt)) retVal = "TSE";
1127    else if("MUT".equalsIgnoreCase(mkt)) retVal = "MUTF_CA";
1128    else if("MUTF".equalsIgnoreCase(mkt)) retVal = "MUTF_CA";
1129
1130    return retVal;
1131  }
1132
1133
1134  /** Create the Portfolio Summary data string for the CR account for today.
1135    * It returns a multi-line result-set string with the first line as the column headings and the remaining lies of data<br>
1136    *  FriendlyName, Currency, BookValue, TradeCash,  MarketValue, EquityValue, UnrealizedGainLoss, UnrealizedGainLossPercent, numHoldings, date<br>
1137    *  "#2V9669V6 - TFSA", 50.5, 10.1, 66.4, 15.9, 8, "10/27/2017"
1138    *
1139    * @return the delimited  resultSet OR '' if not found; the resultset is a comma delimited stockName, symbol, CRQuerySymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate
1140    **/
1141  public String getPortfolioSummaryString()
1142  {
1143    String retVal = "";
1144    boolean success = false;
1145    JsonObject dataHome = null;
1146    if(holdings==null)
1147    {
1148      if(!"".equals(getUsername()) && !"".equals(getPassword()))
1149        success = doLogin();
1150      else
1151        System.out.println("ERROR reading username and password");
1152
1153      if(success)
1154      {
1155        String result = doScrape();
1156        JsonObject jsO = toJsonObject(result);
1157        JsonArray jsA = jsO.getJsonArray("Data");
1158        dataHome = jsA.getJsonObject(0);
1159        //holdings = dataHome.getJsonArray("Holdings");
1160      }
1161    }
1162    else
1163      System.out.println("Already Logged in, using existing holdings["+holdings.size()+"]");
1164
1165    if(dataHome!=null) // && holdings!=null)
1166    {
1167      retVal+="\"friendlyName\"";
1168      retVal+=COL_DELIM;
1169      retVal+="\"Currency\"";
1170      retVal+=COL_DELIM;
1171      retVal+="\"BookValue\"";
1172      retVal+=COL_DELIM;
1173      retVal+="\"TradeCash\"";
1174      retVal+=COL_DELIM;
1175      retVal+="\"MarketValue\"";
1176      retVal+=COL_DELIM;
1177      retVal+="\"EquityValue\"";
1178      retVal+=COL_DELIM;
1179      retVal+="\"UnrealizedGainLoss\"";
1180      retVal+=COL_DELIM;
1181      retVal+="\"UnrealizedGainLossPercent\"";
1182      retVal+=COL_DELIM;
1183      retVal+="\"NumberOfHoldings\"";
1184      retVal+=COL_DELIM;
1185      retVal+="\"date\"";
1186      retVal+="\n";
1187
1188      retVal+="\""+dataHome.getString("FriendlyName")+"\"";
1189      retVal+=COL_DELIM;
1190      retVal+=""+dataHome.getJsonString("Currency");
1191      retVal+=COL_DELIM;
1192      retVal+=""+dataHome.getJsonNumber("BookValue");
1193      retVal+=COL_DELIM;
1194      retVal+=""+dataHome.getJsonNumber("TradeCash");
1195      retVal+=COL_DELIM;
1196      retVal+=""+dataHome.getJsonNumber("MarketValue");
1197      retVal+=COL_DELIM;
1198      retVal+=""+dataHome.getJsonNumber("EquityValue");
1199      retVal+=COL_DELIM;
1200      retVal+=""+dataHome.getJsonNumber("UnrealizedGainLoss");
1201      retVal+=COL_DELIM;
1202      retVal+=""+dataHome.getJsonNumber("UnrealizedGainLossPercent");
1203      retVal+=COL_DELIM;
1204      retVal+=""+(holdings!=null?holdings.size():"0");
1205      retVal+=COL_DELIM;
1206      retVal+="\""+dateStr_+"\"";
1207
1208      /*
1209      for(int i=0; i<holdings.size();i++)
1210      {
1211        JsonObject stock = holdings.getJsonObject(i);
1212        if(stock!=null)
1213        {
1214           String currSymbol = stock.getJsonString("Symbol").toString().substring(1,stock.getJsonString("Symbol").toString().length()-1);
1215            System.out.println(currSymbol+" : "+stock.getJsonNumber("Quantity").toString()+" @ "+
1216                               stock.getJsonString("Price").toString().substring(1,stock.getJsonString("Price").toString().length()-1)+
1217                               " = "+stock.getJsonNumber("MarketValue").toString());
1218        }
1219      }
1220      */
1221    }
1222
1223    return retVal;
1224  }
1225
1226
1227
1228  /** Create the Portfolio data string for the CR account for today.
1229    * It returns a multi-line result-set string with the first line as the column headings and the remaining lies of data<br>
1230    *  stockName, symbol, CRQuerySymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate<br>
1231    *  Hive Tech, HIVE, HIVE, 500, 43.4, CA, 1502.40, 2900.84, 1397.44, 83.4, "10/27/2017"
1232    *
1233    * @return the delimited  resultSet OR '' if not found; the resultset is a comma delimited stockName, symbol, CRQuerySymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate
1234    **/
1235  public String getPortfolioString(){return getPortfolioString(true);}
1236
1237
1238
1239  /** Create the Portfolio data string for the CR account for today.
1240    * It returns a multi-line result-set string with the first line as the column headings and the remaining lies of data<br>
1241    *  stockName, symbol, CRExchangeSymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate<br>
1242    *  Hive Tech, HIVE, V, 500, 43.4, CA, 1502.40, 2900.84, 1397.44, 83.4, 10-27-2017
1243    *
1244    * @param resultSetOnly true to return only the default delimited resultSet OR false to send a longer more readable string
1245    * @return the delimited  resultSet OR '' if not found; the resultset is a comma delimited stockName, symbol, CRQuerySymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate
1246    **/
1247  public String getPortfolioString(boolean resultSetOnly)
1248  {
1249    String retVal = "stockName"+COL_DELIM+"symbol"+COL_DELIM+"CRQuerySymbol"+COL_DELIM+"numShares"+COL_DELIM+"currentPrice"+COL_DELIM+
1250                    "currency"+COL_DELIM+"bookValue"+COL_DELIM+"marketValue"+COL_DELIM+"gain"+COL_DELIM+"gainPercent"+COL_DELIM+"currentDate\n";
1251    if(!resultSetOnly) retVal = "\n\n---------------------------------\n    Portfolio Summary\n---------------------------------\n";
1252
1253    DecimalFormat dfp = new DecimalFormat( "##0" );
1254    DecimalFormat dfe = new DecimalFormat( "##0.000" );
1255    boolean success = false;
1256
1257    if(holdings==null)
1258    {
1259      if(!"".equals(getUsername()) && !"".equals(getPassword()))
1260        success = doLogin();
1261      else
1262        System.out.println("ERROR reading username and password");
1263
1264      if(success)
1265      {
1266        String result = doScrape();
1267        JsonObject jsO = toJsonObject(result);
1268        JsonArray jsA = jsO.getJsonArray("Data");
1269        dataHome = jsA.getJsonObject(0);
1270        holdings = dataHome.getJsonArray("Holdings");
1271      }
1272    }
1273    else
1274      System.out.println("Already Logged in, using existing holdings["+holdings.size()+"]");
1275
1276    if(dataHome!=null && holdings!=null)
1277    {
1278      if(!resultSetOnly)
1279      {
1280        String friendlyName = dataHome.getString("FriendlyName");
1281
1282        retVal+="    FriendlyName       : "+friendlyName;
1283        retVal+="\n";
1284        retVal+="    MarketValue        : "+dataHome.getJsonNumber("MarketValue");
1285        retVal+="\n";
1286        retVal+="    TradeCash          : "+dataHome.getJsonNumber("TradeCash");
1287        retVal+="\n";
1288        retVal+="    BookValue          : "+dataHome.getJsonNumber("BookValue");
1289        retVal+="\n";
1290        retVal+="    ------------------   ------------------------";
1291        retVal+="\n";
1292        retVal+="    UnrealizedGainLoss : "+dataHome.getJsonNumber("UnrealizedGainLoss");
1293        retVal+="\n";
1294        retVal+="    UnrealizedGain%    : "+
1295                dfe.format(dataHome.getJsonNumber("UnrealizedGainLossPercent").doubleValue()*100.0);
1296        retVal+="\n";
1297        retVal+="\n Holdings Summary\n - - - - - - - - - - - - -\n";
1298      }
1299
1300      String currSymbol = "";
1301      String priceStr = "";
1302      String currencySymbol = "";
1303      JsonObject stock = null;
1304      for(int i=0; i<holdings.size();i++)
1305      {
1306        stock = holdings.getJsonObject(i);
1307        if(stock!=null)
1308        {
1309          currSymbol = stock.getJsonString("Symbol").toString().substring(1,stock.getJsonString("Symbol").toString().length()-1);
1310          if(!resultSetOnly)
1311          {
1312            retVal+="   "+currSymbol+" : "+stock.getJsonNumber("Quantity").toString()+" @ "+
1313                  stock.getJsonString("Price").toString().substring(1,stock.getJsonString("Price").toString().length()-1)+
1314                  " = "+stock.getJsonNumber("MarketValue").toString();
1315            retVal+="\n";
1316          }
1317          else
1318          {
1319            if(right(stock.getJsonString("Price").toString() ,2).equals("U\""))
1320            {
1321              priceStr = stock.getJsonString("Price").toString().substring(1,stock.getJsonString("Price").toString().length()-2);
1322              currencySymbol = "US$";
1323            }
1324            else
1325            {
1326              priceStr = stock.getJsonString("Price").toString().substring(1,stock.getJsonString("Price").toString().length()-1);
1327              currencySymbol = "C$";
1328            }
1329            //stockName, symbol, CRExchangeSymbol,  numShares, currentPrice, currency, bookValue, marketValue, gain, gainPercent, currentDate
1330            retVal+=stock.getString("SymbolDescription").toString()+COL_DELIM+
1331                    currSymbol+COL_DELIM+
1332                    stock.getString("Exchange").toString()+COL_DELIM+
1333                    stock.getJsonNumber("Quantity").toString()+COL_DELIM+
1334                    priceStr+COL_DELIM+
1335                    currencySymbol+COL_DELIM+
1336                    stock.getJsonNumber("BookValue").toString()+COL_DELIM+
1337                    stock.getJsonNumber("MarketValue").toString()+COL_DELIM+
1338                    stock.getJsonNumber("UnrealizedGainLoss").toString()+COL_DELIM+
1339                    dfe.format(stock.getJsonNumber("UnrealizedGainLossPercent").doubleValue()*100.0)+COL_DELIM+
1340                    dateStr_;
1341            retVal+="\n";
1342          }
1343        }
1344      }
1345      if(!resultSetOnly) retVal+="\n---------------------------\nHoldings Details:\n---------------------------\n"+
1346                                  prettyJson(holdings.toString());
1347    }
1348
1349    return retVal;
1350  }
1351
1352
1353  public  String right(String value, int length)
1354  {
1355    // To get right characters from a string, change the begin index.
1356    return value.substring(value.length() - length);
1357  }
1358
1359
1360  /** Test method to do whatever tests I want. **/
1361  @Override
1362  protected void test(String[] args)
1363  {
1364    super.debugOut_=true;
1365    debugOut_=true;
1366
1367    //NASDAQ:FCEL   CVE:HIVE   MUTF_CA:AGF201 TSE:LUN
1368    if(true)
1369    {
1370      String symbol = "AGF201";
1371      String market = "MUTF_CA";
1372      double result = getQuote(symbol,market);
1373      System.out.println("Test Quote For "+symbol+"="+result);
1374      //writeStringToFile(result,"gQuote-"+symbol+".txt");
1375    }
1376    else if(false)
1377    {
1378      System.out.println("...");
1379      try
1380      {
1381
1382      }
1383      catch (Exception ex)
1384      {
1385        System.out.println("NO GO...");
1386        ex.printStackTrace();
1387      }
1388    }
1389  }
1390
1391
1392  public static void main(String[] args)
1393  {
1394    GoogleFinanceScraper instance = new GoogleFinanceScraper();
1395
1396    if(args.length>0 && args[0].toLowerCase().equals("-t"))
1397      instance.test(args);
1398    else if(args.length>1 && args[0].equalsIgnoreCase("quote"))
1399    {
1400      System.out.println("\n\nQuote For market:symbol "+args[1]+":"+args[2]+" = "+instance.getQuote(args[2],args[1]));
1401      System.out.println("Volume For market:symbol "+args[1]+":"+args[2]+" = "+instance.getVolume(args[2],args[1]));
1402      System.out.println("Open Price For market:symbol "+args[1]+":"+args[2]+" = "+instance.getOpen(args[2],args[1]));
1403      System.out.println("Days LOW Price For market:symbol "+args[1]+":"+args[2]+" = "+instance.getDaysLow(args[2],args[1]));
1404      System.out.println("Days High Price For market:symbol "+args[1]+":"+args[2]+" = "+instance.getDaysHigh(args[2],args[1]));
1405      System.out.println("Dividend Yield % For market:symbol "+args[1]+":"+args[2]+" = "+instance.getLatestDividend(args[2],args[1]));
1406  }
1407    else
1408    {
1409      boolean success = false;
1410      if(!"".equals(instance.getUsername()) && !"".equals(instance.getPassword()))
1411        success = instance.doLogin();
1412      else
1413        System.out.println("ERROR reading username and password");
1414
1415      if(success)
1416      {
1417        String result = instance.doScrape();
1418        System.out.println("Saving Scraped page results to file: "+"gPageContent-"+instance.dateStr_+".json");
1419        writeStringToFile(prettyJson(result),"gPageContent-"+instance.dateStr_+".json");
1420
1421        System.out.println(instance.getPortfolioString());
1422      }
1423    }
1424  }
1425}