• Solutions
    • FERC XBRL Reporting
    • FDTA Financial Reporting
    • SEC Compliance
    • Windows Clipboard Management
    • Legato Scripting
  • Products
    • GoFiler Suite
    • XBRLworks
    • SEC Exhibit Explorer
    • SEC Extractor
    • Clipboard Scout
    • Legato
  • Education
    • Training
    • SEC and EDGAR Compliance
    • Legato Developers
  • Blog
  • Support
  • Skip to blog entries
  • Skip to archive page
  • Skip to right sidebar

Friday, April 12. 2019

LDC #131: Building a Section 16 Data Scraper, Part 4: Dialog

It’s time to finish up our full script for our Section 16 data scraper. In the first three parts of this series we completed a base script that would go out to the SEC’s website, download all of the filings from a particular CIK, cache all of the Section 16 filings, find all of the reported transactions, put those transactions into a CSV file, and then read all of those transactions and figure out which of those transactions are current holdings. This week we are going to finish up by creating a dialog and adding our script functionality into the dialog.


Scott has recently finished up a series talking about creating dialog boxes and data controls within dialog boxes. You can read his series starting with Part 1 here. Because a lot of the information I would normally cover here has been explained in detail in his series, today’s post is going to be a lot more about the code changes we have made to allow our script to work within the confines of a dialog.


The major points for this week include: changing our main function to set up the hook for our real function so that we can access the dialog through the ribbon, adding our dialog resource to the script, and adding all of the requisite functions so that we can have the dialog access all of the script functions we have been creating in the last few posts. In addition we have a few changes that have been made in the data we collect so that it is a better user experience.


Before we look at the finished product we need to take a moment and talk about what we want to get out of this script. Going in to this project my requirements were to create a script that would take a CIK and give back a list of current holdings for that CIK. My eventual goal was to take this groundwork and use it later to create a more comprehensive functionality to ease the creation of Section 16 filings in GoFiler. As we went on through the weeks my requirements and end goals changed as my understanding on Section 16 changed. With that in mind there are a few important points to remember about this script.


First off, this script has given me some insight as to how typos within filings can haunt you for the rest of time. The way that we are checking for holdings starts with new filings and checks older filings against the security name, issuer CIK, and ownership type. If a typo is made in the way that a security is named, that filing will show up as a current holding even though it should not be.


Secondly, there are a lot of people who do not understand the SEC’s requirements for Section 16 filings. In my attempt to find good examples of the script working correctly I continued to stumble across people who were not meeting all of the requirements, and so were not good examples. However, the fact that I struggled to find a good example means that there is a widespread problem with correctly filing Section 16.


Finally, this script as it currently is will only help with the creation of Form 5 holding documents. You can take the output of the script and paste it into any Section 16 document, but this will copy the transaction data to the clipboard, so all of the numbers and dates will have to be updated manually. In the end, the biggest use of this script is to easily look up a reporting owners current holdings and issuers, but additional work will have to be done to allow easy creation of filings.


Here’s this week’s final script:


/*****************************************************************************************************************

        Section 16 Data Collector
        -------------------------
        
        Revision:

            01-04-19  JCK       Initial creation - get filings from SEC
            02-08-19  JCK       Part 2 - Store information
            03-29-19  JCK       Part 3 - Refactoring and getting holdings
            04-12-19  JCK       Part 4 - Dialog Creation

        Notes:
        
            -Stage 4
                                  (c) 2019 Novaworks, LLC. All Rights Reserved.


*****************************************************************************************************************/

#define COL_TORH        0
#define COL_AORD        1
#define COL_TDATE       2
#define COL_TTYPE       3
#define COL_DORI        4
#define COL_NUMTRANS    5
#define COL_NUMOWNED    6
#define COL_CIK         7
#define COL_NAME        8
#define COL_DND         9
#define SCRIPT_INI      GetScriptFolder()+"ReportingOwnerHoldings.ini"
#define CACHE_BASE      AddPaths(GetScriptFolder(), "Cache\\")


    handle              hSheet;
    handle              file;
    string              s16filings[];                   //Table of all filings
    string              s16holdings[][];                //Table of current holdings
    string              currentholding[][];             //Current Holding
    string              cache;                          //Location of cache
    string              c_cik;                          //CIK of Reporting Owner
    boolean             copyAll;

    boolean             is_section_16           (string type);
    void                add_transaction         (string issuer);
    void                add_holding             (string issuer, string date);
    int                 populate_filings        ();
    void                get_current_holdings    ();
    void                parse_filings           ();
    
    int                 run                     (int f_id, string opts);
    int                 ro_reload_data          ();
    int                 ro_action               (int c_id, int c_action);
    int                 ro_load                 ();
    int                 ro_validate             ();
    
    int setup() {
      
      string            fnScript;
      string            item[10];
      int               rc;
                                                                        /* ** Add Menu Item                     */
                                                                        /*  * Define Function                   */
      item["Code"] = "EXTENSION_RO_HOLDINGS";                           /* Function Code                        */
      item["MenuText"] = "&Retrieve Holdings";                          /* Menu Text                            */
      item["Description"] = "<B>Retrieve Holdings</B>\r\rRetrieve and List Current Holdings";
      item["Class"] = "Extension";                                      /* Add to Main Ribbon                   */
                                                                        /*  * Check for Existing                */
      rc = MenuFindFunctionID(item["Code"]);                            /* Look for existing                    */
      if (IsNotError(rc)) {                                             /* Was already be added                 */
        return ERROR_NONE;                                              /* Exit                                 */
        }                                                               /* end error                            */
                                                                        /*  * Registration                      */
      rc = MenuAddFunction(item);                                       /* Add the item                         */
      if (IsError(rc)) {                                                /* Was already be added                 */
        return ERROR_NONE;                                              /* Exit                                 */
        }                                                               /* end error                            */
      fnScript = GetScriptFilename();                                   /* Get the script filename              */
      MenuSetHook(item["Code"], fnScript, "run");                       /* Set the Hook                         */
      return ERROR_NONE;
      }
      
    int main() {
      string    s1;
      int       rc;
                                                                        /*  * Processing from GoFiler           */
      s1 = GetScriptParent();                                           /* Get the parent                       */
      if (s1 == "LegatoIDE") {                                          /* Is run from the IDE (debug)          */
        rc = MenuFindFunctionID("EXTENSION_RO_HOLDINGS");               /* Look for existing                    */
        if (IsError(rc)) {                                              /* No every hooked                      */
          setup();                                                      /* Add to the menu                      */
          }                                                             /* end no function                      */
        else {                                                          /* Was hooked                           */
          MenuDeleteHook("EXTENSION_RO_HOLDINGS");                      /* Cause to run hook from development   */
        s1 = GetScriptFilename();                                       /* Get the script filename (us)         */
          MenuSetHook("EXTENSION_RO_HOLDINGS", s1, "run");              /* Set the Hook                         */
          }                                                             /* end already defined                  */
        MessageBox('i', "Hook running on IDE");                         /* Status                               */
        }                                                               /* end IDE run                          */
      return ERROR_NONE;                                                /* Return value (does not matter)       */
      }
      
    int run(int f_id, string mode) { 
    
      //creation variables
      int               count;                          //Counting Variable
      int               max;
      int               rc;
      string            s1;
      handle            hCB;
      
      //If not preprocess leave
      if (mode != "preprocess") {
        return ERROR_NONE;
        }
      
      //Clear globals
      ArrayClear(currentholding);
      copyAll = false;
      
      //Get old CIK
      c_cik = GetSetting("Settings", "CIK");                                                            
      
      //Start Dialog
      rc = DialogBox("RODLG1", "ro_");

      //If CIK entered, store it
      if (c_cik != "") {
        PutSetting("Settings", "CIK", c_cik);
        }
      
      //If close button hit leave
      if (rc == ERROR_CANCEL) { return ERROR_CANCEL; }
      //Get how many selected
      max = ArrayGetAxisDepth(currentholding, AXIS_ROW);
      count = 0;
      //Set CSV heading
      s1 = "Type of Entry, Title of Security, Transaction Date, Transaction Code, Transaction Acquired Code, Transaction Acquired, Transaction Disposed Code, Transaction Disposed, Transaction Shares, Transaction Post, Ownership Form\n";
      //Go through all selected
      while (count < max) {
        //If copy all - set as holding
        if (copyAll) {
          s1 += "Holding,";
          }
        else {
          s1 += currentholding[count][COL_TORH] + ",";
          }
        s1 += "\"" + currentholding[count][COL_NAME] + "\",";
        if (!copyAll) {
          if (currentholding[count][COL_TORH] == "Transaction") {
            s1 += currentholding[count][COL_TDATE] + ",";
            }
          else {
            s1 += ",";
            }
          s1 += currentholding[count][COL_TTYPE] + ",";
          if (currentholding[count][COL_AORD] == "A") {
            s1 += currentholding[count][COL_AORD] + ",";
            s1 += currentholding[count][COL_NUMTRANS] + ",";
            s1 += ",,";
            }
          else {
            s1 += ",,";
            s1 += currentholding[count][COL_AORD] + ",";
            s1 += currentholding[count][COL_NUMTRANS] + ",";    
            }
          s1 += currentholding[count][COL_NUMTRANS] + ",";
          }
        else {
          s1 += ",,,,,,,";
          }
        s1 += currentholding[count][COL_NUMOWNED] + ",";
        s1 += currentholding[count][COL_DORI] + "\n";
        count++;
        }
      //Create clipboard
      hCB = ClipboardCreate();
      if (IsError()) {                                          
        rc = GetLastError();                                            
        MessageBox('x', "Cannot access clipboard (0x%08X).", rc);       
        return ERROR_NONE;                                              
        }
      //Put CSV on clipboard
      ClipboardSetCSV(hCB, s1);
      //Close handle
      CloseHandle(hCB);
      return ERROR_CANCEL;
      }
    
#beginresource
#define RO_REFRESH      103
#define RO_LIST         101
#define RO_CIK          102
#define ROREFRESH       103
#define RO_HOLDINGCOPY  104
#define ROHOLDINGCOPY   104

RODLG1 DIALOGEX 0, 0, 539, 195
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Retrieve Reporting Owner Holding"
FONT 8, "MS Sans Serif"
{
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 480, 12, 50, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 480, 168, 50, 14
 CONTROL "Copy All", RO_HOLDINGCOPY, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 480, 32, 50, 14
 CONTROL "Refresh Holdings", RO_REFRESH, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 169, 11, 63, 14
 CONTROL "Reporting Owner CIK:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 14, 72, 8, 0
 CONTROL "", RO_CIK, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER, 84, 12, 72, 12, 0
 CONTROL "", RO_LIST, "data_control", LBS_MULTIPLESEL | LBS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 12, 36, 460, 148, 0
 CONTROL "Configuration", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 3, 46, 9, 0
 CONTROL "Information", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 24, 40, 9, 0
 CONTROL "Frame1", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 48, 7, 424, 1, 0
 CONTROL "Frame2", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 44, 28, 428, 1, 0
}
#endresource
    
    int ro_load() {
    
                                                                        /*  * Set Up List                       */
      DataControlSetColumnPositions(RO_LIST, 50, 95, 140, 300, 370, 420);/* Set the positions                   */
      DataControlSetColumnHeadings(RO_LIST, "Derivative?",              /* Derivative?                          */
                                            "Date",                     /*   Date                               */
                                            "CIK",                      /*   CIK                                */
                                            "Name",                     /*   Name                               */
                                            "Securities Reported",      /*   securities                         */
                                            "Direct or Indirect");      /*   D or I                             */
      DataControlSetColumnFlags(RO_LIST,  DS_CC_SORT, DS_CC_SORT,       /* Set the sort                         */
                                          DS_CC_SORT, DS_CC_SORT,       /* Set the sort                         */
                                          DS_CC_SORT, DS_CC_SORT);      /* Set the sort                         */
      //Set button names
      EditSetText(IDCANCEL, "Close");
      EditSetText(IDOK, "Copy");
      //Clear global
      ArrayClear(currentholding);
      //Set CIK text if exists
      if (c_cik != "") {
        EditSetText(RO_CIK, c_cik);
        ro_reload_data();
        }
      return ERROR_NONE;
      }
    
    int ro_load_list(){
      
      int       count;
      int       max;
      int       printed;
      string    s1;
      
      //Reset data control
      DataControlResetContent(RO_LIST);
      DataControlSetSortColumn(RO_LIST);
      
      count = 0;
      max = ArrayGetAxisDepth(s16holdings, AXIS_ROW);
      printed = 0;
      //Print out all of our current holdings
      while (count < max) {
        if (s16holdings[count][COL_NUMOWNED] != "0") {
          s1 =  s16holdings[count][COL_DND] + "\t";
          s1 += s16holdings[count][COL_TDATE] + "\t";
          s1 += s16holdings[count][COL_CIK] + "\t";
          s1 += s16holdings[count][COL_NAME] + "\t";
          s1 += s16holdings[count][COL_NUMOWNED] + "\t";
          s1 += s16holdings[count][COL_DORI];
          
          DataControlAddString(RO_LIST, s1);
          DataControlSetRowData(RO_LIST, printed++, count);
          }
        count++;
        }
      return ERROR_NONE;
      }
    
    int ro_reload_data() {
    
      string    test[];
      int       rc;
      string    s2;
    
      //Get text
      c_cik = EditGetText(RO_CIK);
      //Zerofill
      c_cik = FormatString("%010s", c_cik);
      //Set text with zeroes
      EditSetText(RO_CIK, c_cik); 
      //Lookup CIK
      test = EDGARLookupCIK(c_cik);
      rc = GetLastError();
      //Is CIK Valid?
      if (rc == ERROR_EOD) { 
        MessageBox('x', "CIK is not valid.");
        return ERROR_EOD; 
        }
      //Get cache
      cache = AddPaths(CACHE_BASE, c_cik);
      //create cache
      CreateFolders(cache);
      if (IsFolder(cache) == FALSE) {                                   
        AddMessage('x', "Unable to create document cache.");    
        return ERROR_CANCEL;
        }                                                                       
      populate_filings();
      //Parse through filings to find all transactions
      parse_filings();
      //Parse through transactions and holdings to find current number
      get_current_holdings();
      //Put current holdings in dialog
      ro_load_list();
      }
      
    int ro_action(int c_id, int c_action) {
      
      int       ix;
      
      //Refresh CIK
      if (c_id == RO_REFRESH) { 
        ro_reload_data();
        return ERROR_NONE;
        }
      //Double click posts OK
      if (c_id == RO_LIST) {
        if (c_action == DCN_DOUBLE_CLICK) {
          ix = DataControlGetSelectCount(RO_LIST);                      
          if (ix == 0) { return ERROR_NONE; }                           
          DialogPostOK();
          return ERROR_NONE;
          }
        }
      //Copy all button
      if (c_id == RO_HOLDINGCOPY) {
        copyAll = true;
        DialogPostOK();
        return ERROR_NONE;
        }
      return ERROR_NONE;
      }
    
    int ro_validate() {
      int       ix[][];
      int       count;
      int       iy;
      int       currow;
      
      //Check how many rows selected
      count = DataControlGetSelectCount(RO_LIST);
      //If copy all, get how many holdings total
      if (copyAll) {
        count = ArrayGetAxisDepth(s16holdings, AXIS_ROW);
        }
      //If not selected
      if (count == 0) {
        MessageBox('x', "Select at least one item in the holdings list to continue.");
        return ERROR_SOFT | RO_LIST;
        }
      //Go through all selected data
      ix = DataControlGetSelectList(RO_LIST);
      iy = 0;
      while (iy < count) {
        if(copyAll) {
          currow = iy;
          }
        else {
          currow = ix[iy][0];
          }
        //Put selected data into array
        currentholding[iy][COL_TORH] = s16holdings[currow][COL_TORH];
        currentholding[iy][COL_AORD] = s16holdings[currow][COL_AORD];
        currentholding[iy][COL_TDATE] = s16holdings[currow][COL_TDATE];
        currentholding[iy][COL_TTYPE] = s16holdings[currow][COL_TTYPE];
        currentholding[iy][COL_DORI] = s16holdings[currow][COL_DORI];
        currentholding[iy][COL_NUMTRANS] = s16holdings[currow][COL_NUMTRANS];
        currentholding[iy][COL_NUMOWNED] = s16holdings[currow][COL_NUMOWNED];
        currentholding[iy][COL_CIK] = s16holdings[currow][COL_CIK];
        currentholding[iy][COL_NAME] = s16holdings[currow][COL_NAME];
        currentholding[iy][COL_DND] = s16holdings[currow][COL_DND];
        iy++;
      }
      return ERROR_NONE;
      }
    //Returns true if S16 file type, false if anything else
    boolean is_section_16(string type) {
    
      switch (type) {
        case "3":
          return true;
        case "4":
          return true;
        case "5":
          return true;
        case "3/A":
          return true;
        case "4/A":
          return true;
        case "5/A":
          return true;
        }
      return false;
      }
    
    void parse_filings() {
      int               count, max, rc;
      string            date, cik;
      string            parser;
      
      //Get number of filings
      count = ArrayGetAxisDepth(s16filings) - 1;
      //Create data sheet
      hSheet = DataSheetCreate(2, 10);
      //Set the header row
      DataSheetSetCellText(hSheet, 0, COL_TORH, "Transaction or Holding");
      DataSheetSetCellText(hSheet, 0, COL_AORD, "Acquisition or Disposition");
      DataSheetSetCellText(hSheet, 0, COL_TDATE, "Transaction Date");
      DataSheetSetCellText(hSheet, 0, COL_TTYPE, "Transaction Type");
      DataSheetSetCellText(hSheet, 0, COL_DORI, "Direct or Indirect Ownership");
      DataSheetSetCellText(hSheet, 0, COL_NUMTRANS, "Number of Securities Transacted");
      DataSheetSetCellText(hSheet, 0, COL_NUMOWNED, "Number of Securities Owned");
      DataSheetSetCellText(hSheet, 0, COL_CIK, "Issuer CIK");
      DataSheetSetCellText(hSheet, 0, COL_NAME, "Security Name");
      DataSheetSetCellText(hSheet, 0, COL_DND, "Derivative or Non-Dirivative");
      
       //Open progress bar
      ProgressOpen("Reading Filings");
      ProgressSetPhaseCount(count);
      max = count;
      
      //Go through filings
      while (count >= 0) {
        //Update progress
        ProgressSetPhase(max-count);
        ProgressSetStatus(FormatString("Reading file %d of %d", max-count, max));
        ProgressUpdate(max-count, max);
        //AddMessage("Parsing file %s", s16filings[count]);
        date = "";
        //Create SGML parser
        file = SGMLCreate();
        //Set parse to this file
        rc = SGMLSetFile(file, s16filings[count]);
        //Check for errors
        if (rc != ERROR_NONE) {
          AddMessage("Error on Setting File %s: 0x%08X", s16filings[count], rc);
          }
        //Get first element
        parser = SGMLNextElement(file);
        rc = GetLastError();
        //Check for issuer CIK
        while (parser != "<periodOfReport>" && rc == ERROR_NONE) {
          parser = SGMLNextElement(file);
          rc = GetLastError();
          }
        //Get CIK
        date = SGMLNextItem(file);
        //Check for issuer CIK
        while (parser != "<issuerCik>" && rc == ERROR_NONE) {
          parser = SGMLNextElement(file);
          rc = GetLastError();
          }
        //Get CIK
        cik = SGMLNextItem(file);
        if (rc != ERROR_NONE) {
          AddMessage("Error on retrieving CIK: 0x%08X", rc);
          }
        
        //Until end of file
        while (rc == ERROR_NONE) {
          //check if element is beginning of transaction
          if (parser == "<nonDerivativeTransaction>" || parser == "<derivativeTransaction>") {
            //Add transaction
            add_transaction(cik);
            }
          if (parser == "<nonDerivativeHolding>" || parser == "<derivativeHolding>") {
            //Add transaction
            add_holding(cik, date);
            }
          
          //Next element
          parser = SGMLNextElement(file);
          rc = GetLastError();
          }
        //Done with SGML parser
        CloseHandle(file);
        //Next file
        count--;
        }
      
      //Export data to CSV
      rc = DataSheetExport(hSheet, AddPaths(cache, c_cik + ".csv"));
      if (rc != ERROR_NONE) {
        AddMessage("Error on Creating CSV: 0x%08X", rc);
        }
      CloseHandle(hSheet);
      //AddMessage("Finished Parsing files from CIK: %s", TEST_CIK);
      ProgressClose();
    }
    
    void add_transaction(string issuer) {
    
      string            aord, date, ttype, dori, numtrans, numowned, secname, dnd;
      string            parser;
      int               rc;
      
      //Initialize variables
      aord = "";
      date = "";  
      ttype = "";   
      dori = "";
      numtrans = "";
      numowned = "";  
      secname = ""; 
      dnd = "";
      
      //Get next element
      parser = SGMLNextElement(file);
      //Check for end of the transaction
      while (parser != "</nonDerivativeTransaction>" && parser != "</derivativeTransaction>" && rc == ERROR_NONE){
        if (parser == "<securityTitle>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          secname = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            secname += SGMLNextItem(file);
            }
          secname = ReplaceInString(secname, "</value>", "");
          }
        if (parser == "<transactionDate>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          date = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            date += SGMLNextItem(file);
            }
          date = ReplaceInString(date, "</value>", "");
          }
        if (parser == "<transactionCode>"){
          ttype = SGMLNextItem(file);
        }
        if (parser == "<transactionShares>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          numtrans = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            numtrans += SGMLNextItem(file);
            }
          numtrans = ReplaceInString(numtrans, "</value>", "");
          }
        if (parser == "<transactionAcquiredDisposedCode>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          aord = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            aord += SGMLNextItem(file);
            }
          aord = ReplaceInString(aord, "</value>", "");
          }
        if (parser == "<sharesOwnedFollowingTransaction>"){ 
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          numowned = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            numowned += SGMLNextItem(file);
            }
          numowned = ReplaceInString(numowned, "</value>", "");
          }
        if (parser == "<directOrIndirectOwnership>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          dori = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            dori += SGMLNextItem(file);
            }
          dori = ReplaceInString(dori, "</value>", "");
          }
        parser = SGMLNextElement(file);
        rc = GetLastError();
        }
      //If non-derivative
      if (parser == "</nonDerivativeTransaction>") {
        dnd = "Non-Derivative";
        }
      else {
        dnd = "Derivative";
        }
      //Insert a row underneath the header row
      DataSheetRowInsert(hSheet, 1);
      //Insert all of the data
      DataSheetSetCellText(hSheet, 1, COL_TORH, "Transaction"); 
      DataSheetSetCellText(hSheet, 1, COL_AORD, aord); 
      DataSheetSetCellText(hSheet, 1, COL_TDATE, date);
      DataSheetSetCellText(hSheet, 1, COL_TTYPE, ttype);
      DataSheetSetCellText(hSheet, 1, COL_DORI, dori);
      DataSheetSetCellText(hSheet, 1, COL_NUMTRANS, numtrans);
      DataSheetSetCellText(hSheet, 1, COL_NUMOWNED, numowned);
      DataSheetSetCellText(hSheet, 1, COL_CIK, issuer);
      DataSheetSetCellText(hSheet, 1, COL_NAME, secname);
      DataSheetSetCellText(hSheet, 1, COL_DND, dnd);
      }

    void add_holding(string issuer, string date) {
    
      string            aord, ttype, dori, numtrans, numowned, secname, dnd;
      string            parser;
      int               rc;
      
      //Initialize variables
      dori = "";
      numowned = "";  
      secname = ""; 
      dnd = "";
      
      //Get next element
      parser = SGMLNextElement(file);
      //Check for end of the transaction
      while (parser != "</nonDerivativeHolding>" && parser != "</derivativeHolding>" && rc == ERROR_NONE){
        if (parser == "<securityTitle>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          secname = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            secname += SGMLNextItem(file);
            }
          secname = ReplaceInString(secname, "</value>", "");
          }
        if (parser == "<sharesOwnedFollowingTransaction>"){ 
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          numowned = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            numowned += SGMLNextItem(file);
            }
          numowned = ReplaceInString(numowned, "</value>", "");
          }
        if (parser == "<directOrIndirectOwnership>"){
          while (parser != "<value>") {
            parser = SGMLNextItem(file);
            }
          dori = SGMLNextItem(file);
          while (SGMLGetItemType(file) != SPI_TYPE_TAG){
            dori += SGMLNextItem(file);
            }
          dori = ReplaceInString(dori, "</value>", "");
          }
        parser = SGMLNextElement(file);
        rc = GetLastError();
        }
      //If non-derivative
      if (parser == "</nonDerivativeHolding>") {
        dnd = "Non-Derivative";
        }
      else {
        dnd = "Derivative";
        }
      //Insert a row underneath the header row
      DataSheetRowInsert(hSheet, 1);
      //Insert all of the data
      DataSheetSetCellText(hSheet, 1, COL_TORH, "Holding"); 
      DataSheetSetCellText(hSheet, 1, COL_TDATE, date);
      DataSheetSetCellText(hSheet, 1, COL_DORI, dori);
      DataSheetSetCellText(hSheet, 1, COL_NUMOWNED, numowned);
      DataSheetSetCellText(hSheet, 1, COL_CIK, issuer);
      DataSheetSetCellText(hSheet, 1, COL_NAME, secname);
      DataSheetSetCellText(hSheet, 1, COL_DND, dnd);
      }
      
    int populate_filings() {
      //creation variables
      string            files[];                        //Files from SEC
      string            name;                           //Name of file
      boolean           s16Check;                       //Placeholder boolean
      int               rc;                             //Error Checker
      string            base;                           //String for name storage
      int               count;
      int               numfiles;
      int               max;
      
      ArrayClear(s16filings);
      
      //Get the archive list   
      files = EDGARFetchArchiveList(c_cik);
      rc = GetLastError();
      //Check is CIK exists
      if (rc == (ERROR_REMOTE | ERROR_FILE_NOT_FOUND)) {
        MessageBox("CIK is not valid.");
        return ERROR_CANCEL;
        }
      //If too many filings
      if (rc == ERROR_OVERFLOW) {
        files = EDGARFetchArchiveList(c_cik, FALSE, 0, 0, TRUE);
        }
      
      //Initialize needed variables                                                                     
      max = ArrayGetAxisDepth(files);
      count = 0; 
      numfiles = 0;
      
      //Open progress bar
      ProgressOpen("Getting Files");
      ProgressSetPhaseCount(max-1);
      
      //While there are still files to go through
      while (count < max) {
        //Update progress
        ProgressSetPhase(count);
        ProgressSetStatus(FormatString("Getting file %d of %d", count, max));
        ProgressUpdate(count, max);
        
        //Get filename
        base = ReplaceInString(GetFilename(files[count]), ".txt", ".xml");
        //Check if already downloaded
        if (DoesFileExist(AddPaths(cache, base)) == FALSE) {
          //Open archive
          file = EDGARArchiveOpen(files[count]);
          //Make sure a S16 filing
          s16Check = is_section_16(EDGARArchiveGetDocType(file, 0));
          //If s16
          if (s16Check) { 
            if (EDGARArchiveGetDocFileType(file, 0) == FT_XML) {
              //Set name of XML file
              name = AddPaths(cache, EDGARArchiveGetProperty(file, "accession_number") + ".xml");
              //Get XML file and save it locally
              EDGARArchiveGetDocFile(file, 0, name);
              //Add to the list
              s16filings[numfiles++] = name;
              }
            }
          CloseHandle(file);
          }
        else {
          //Add cached file to list
          s16filings[numfiles++] = AddPaths(cache, base);
          }
        count++;
        }
      //Close progress
      ProgressClose();
      
      return ERROR_NONE;
      }
      
    //Get current holdings
    void get_current_holdings() {
      int               count;
      int               numholdings;
      int               countholdings;
      int               max;
      int               intcount;
      string            aord, date, ttype, dori, numtrans, numowned, secname, cik, type, dnd;
      string            filings[][];
      boolean           found;
      //Clear the array
      ArrayClear(s16holdings);
      //Get all transactions from filings
      filings = CSVReadTable(AddPaths(cache, c_cik + ".csv"));
      //Set a counter
      max = ArrayGetAxisDepth(filings, AXIS_ROW);
      count = 1;
      //Currently at 0 holdings
      numholdings = 0;
      //Go through all transactions
      while (count < max-1) {
        //get all of the values
        type =     filings[count][COL_TORH];
        aord =     filings[count][COL_AORD];
        date =     filings[count][COL_TDATE];   
        ttype =    filings[count][COL_TTYPE];   
        dori =     filings[count][COL_DORI];    
        numtrans = filings[count][COL_NUMTRANS];
        numowned = filings[count][COL_NUMOWNED];
        cik =      filings[count][COL_CIK];
        secname =  filings[count][COL_NAME];
        dnd =      filings[count][COL_DND];
        //set counter to 0
        countholdings = 0;
        //Not found in current holdings
        found = false;
        //Go through current holdings
        while (countholdings < numholdings && found == false) { 
          //Same CIK?
          if (s16holdings[countholdings][COL_CIK] == cik) {
            //Same security name?
            if (CompareStringsNoCase(s16holdings[countholdings][COL_NAME], secname) == 0 ) {
              if (s16holdings[countholdings][COL_DORI] == dori) {
                //Found already reported
                found = true;
                }
              }
            }
          countholdings++;
          }
        //If not reported yet
        if (found == false && TextToInteger(numowned) > 0) {
          //Set holding 
          s16holdings[numholdings][COL_TORH] = type;
          s16holdings[numholdings][COL_AORD] = aord;
          s16holdings[numholdings][COL_TDATE] = date;  
          s16holdings[numholdings][COL_TTYPE] = ttype;
          s16holdings[numholdings][COL_DORI] = dori;  
          s16holdings[numholdings][COL_NUMTRANS] = numtrans;
          s16holdings[numholdings][COL_NUMOWNED] = numowned;
          s16holdings[numholdings][COL_CIK] = cik;
          s16holdings[numholdings][COL_NAME] = secname;
          s16holdings[numholdings][COL_DND] = dnd;
          numholdings++;
          }
        count++;
        }
      }

If last week our script was getting large at almost 500 lines our script this week is definitely large as it is just over 800 lines of code in total. So we’re not going to go through every single line, but I’m going to highlight any of the overall changes that we have made in order to accommodate our new dialog.


#define COL_DND         9
#define SCRIPT_INI      GetScriptFolder()+"ReportingOwnerHoldings.ini"
#define CACHE_BASE      AddPaths(GetScriptFolder(), "Cache\\")


    handle              hSheet;
    handle              file;
    string              s16filings[];                   //Table of all filings
    string              s16holdings[][];                //Table of current holdings
    string              currentholding[][];             //Current Holding
    string              cache;                          //Location of cache
    string              c_cik;                          //CIK of Reporting Owner
    boolean             copyAll;

    boolean             is_section_16           (string type);
    void                add_transaction         (string issuer);
    void                add_holding             (string issuer, string date);
    int                 populate_filings        ();
    void                get_current_holdings    ();
    void                parse_filings           ();
    
    int                 run                     (int f_id, string opts);
    int                 ro_reload_data          ();
    int                 ro_action               (int c_id, int c_action);
    int                 ro_load                 ();
    int                 ro_validate             ();

The first thing to notice here in our globals is that we have added a few new functions. Run(), ro_reload_data(), ro_action(), ro_load(), and ro_validate(). We have also gotten rid of the ability to pass some variables to our main script functions as those are now stored in newly declared global variables. In addition, we are defining our cache base so that we can easily change where the script is looking later on. Finally, we have declared a new column, COL_DND, where we will store whether a row is a derivative or non-derivative transaction.


Our main function is now completely different as it no longer is the driving force behind the script:


    int main() {
      string    s1;
      int       rc;
                                                                        
      s1 = GetScriptParent();                                           
      if (s1 == "LegatoIDE") {                                          
        rc = MenuFindFunctionID("EXTENSION_RO_HOLDINGS");               
        if (IsError(rc)) {                                              
          setup();                                                      
          }                                                             
        else {                                                          
          MenuDeleteHook("EXTENSION_RO_HOLDINGS");                      
        s1 = GetScriptFilename();                                       
          MenuSetHook("EXTENSION_RO_HOLDINGS", s1, "run");              
          }                                                             
        MessageBox('i', "Hook running on IDE");                         
        }                                                               
      return ERROR_NONE;                                               
      }

This turns into a commonly seen function: create a hook if we are running from the IDE. If we currently running from the IDE and the script has been hooked previously, delete the old hook and make a new hook. This allows us to run the script from the IDE, making changes at will, and still be able to run the hooked function. It also means the script file cannot be an untitled document.


    int setup() {
      
      string            fnScript;
      string            item[10];
      int               rc;
                                                                        
                                                                        
      item["Code"] = "EXTENSION_RO_HOLDINGS";                           
      item["MenuText"] = "&Retrieve Holdings";                      
      item["Description"] = "<B>Retrieve Holdings</B>\r\rRetrieve and List Current Holdings";
      item["Class"] = "Extension";                                      
                                                                        
      rc = MenuFindFunctionID(item["Code"]);                            
      if (IsNotError(rc)) {                                             
        return ERROR_NONE;                                              
        }                                                               
                                                                        
      rc = MenuAddFunction(item);                                       
      if (IsError(rc)) {                                                
        return ERROR_NONE;                                              
        }                                                               
      fnScript = GetScriptFilename();                                   
      MenuSetHook(item["Code"], fnScript, "run");                       
        return ERROR_NONE;
      }

We have also created a setup function. This means that we can put the script into the extensions folder of GoFiler and this script will be run upon the application starting. Our script will create a menu function and add it to our Tools ribbon. We then add in a hook to the function and set it towards our run() function. And speaking of our run() function, let’s take a look at that next:


    int run(int f_id, string mode) { 
    
      //creation variables
      int               count;                          //Counting Variable
      int               max;
      int               rc;
      string            s1;
      handle            hCB;
      
      //If not preprocess leave
      if (mode != "preprocess") {
        return ERROR_NONE;
        }
      
      //Clear globals
      ArrayClear(currentholding);
      copyAll = false;
      
      //Get old CIK
      c_cik = GetSetting("Settings", "CIK");                                                            
      
      //Start Dialog
      rc = DialogBox("RODLG1", "ro_");

This is the first half of the function. We start by checking to see what mode we are in. If we are not preprocessing we leave function immediately. Next we see if a CIK is listed in our settings file. We will put the current CIK into the settings file when the dialog gets closed to that we can repopulate the CIK field when the dialog reopens. This is just a nice way to make a user’s life easier. We open the dialog and wait until the dialog is closed.


      //If CIK entered, store it
      if (c_cik != "") {
        PutSetting("Settings", "CIK", c_cik);
        }
      
      //If close button hit leave
      if (rc == ERROR_CANCEL) { return ERROR_CANCEL; }
      //Get how many selected
      max = ArrayGetAxisDepth(currentholding, AXIS_ROW);
      count = 0;
      //Set CSV heading
      s1 = "Type of Entry, Title of Security, Transaction Date, Transaction Code, Transaction Acquired Code, Transaction Acquired, Transaction Disposed Code, Transaction Disposed, Transaction Shares, Transaction Post, Ownership Form\n";
      //Go through all selected
      while (count < max) {
        //If copy all - set as holding
        if (copyAll) {
          s1 += "Holding,";
          }
        else {
          s1 += currentholding[count][COL_TORH] + ",";
          }
        s1 += "\"" + currentholding[count][COL_NAME] + "\",";
        if (!copyAll) {
          if (currentholding[count][COL_TORH] == "Transaction") {
            s1 += currentholding[count][COL_TDATE] + ",";
            }
          else {
            s1 += ",";
            }
          s1 += currentholding[count][COL_TTYPE] + ",";
          if (currentholding[count][COL_AORD] == "A") {
            s1 += currentholding[count][COL_AORD] + ",";
            s1 += currentholding[count][COL_NUMTRANS] + ",";
            s1 += ",,";
            }
          else {
            s1 += ",,";
            s1 += currentholding[count][COL_AORD] + ",";
            s1 += currentholding[count][COL_NUMTRANS] + ",";    
            }
          s1 += currentholding[count][COL_NUMTRANS] + ",";
          }
        else {
          s1 += ",,,,,,,";
          }
        s1 += currentholding[count][COL_NUMOWNED] + ",";
        s1 += currentholding[count][COL_DORI] + "\n";
        count++;
        }
      //Create clipboard
      hCB = ClipboardCreate();
      if (IsError()) {                                          
        rc = GetLastError();                                            
        MessageBox('x', "Cannot access clipboard (0x%08X).", rc);       
        return ERROR_NONE;                                              
        }
      //Put CSV on clipboard
      ClipboardSetCSV(hCB, s1);
      //Close handle
      CloseHandle(hCB);
      return ERROR_CANCEL;
      }

Once the dialog is closed we check to see if the CIK is filled out. If it is we will put the CIK in our settings file. Then we check to see if the dialog was closed with the “Copy” or the “Close” button. If the “Copy” button was pressed we want to place the selected holdings onto the clipboard as CSV. First we figure out how many rows are selected, which allows us to go through our loop. The first thing to do is set the headings to values that will work for having both derivate and non-derivative filings. Then we go through each selected holding. We are going to have to buttons on our dialog: “Copy All” and “Copy.” We have to plan for both modes. For “Copy All” we are going to change all of our holdings to be holdings on the clipboard. If “Copy” is pressed, we take the value of the last report (transaction or holding). We then go through and add all of our values to the string for each selected row. Finally we create a clipboard, add the CSV to the clipboard, and close the clipboard handle. This means that if a user clicks the “Copy” or “Copy all” buttons they will get the holdings given to them in a manner that allows them to paste the holding into a Section 16 filing.


Now it is time to talk about our dialog. Our dialog looks something like this:


 Holdings Dialog


It consists of two important features: an input to put in a CIK to lookup and a data control where we put our list so that the user can see it. We also have a Copy, Copy All, and a Close button on the side. When the dialog is first referenced using the DialogBox() function the load function is called. In this case the function name is ro_load():


    int ro_load() {
    
                                                                        /*  * Set Up List                       */
      DataControlSetColumnPositions(RO_LIST, 50, 95, 140, 300, 370, 420);/* Set the positions                   */
      DataControlSetColumnHeadings(RO_LIST, "Derivative?",              /* Derivative?                          */
                                            "Date",                     /*   Date                               */
                                            "CIK",                      /*   CIK                                */
                                            "Name",                     /*   Name                               */
                                            "Securities Reported",      /*   securities                         */
                                            "Direct or Indirect");      /*   D or I                             */
      DataControlSetColumnFlags(RO_LIST,  DS_CC_SORT, DS_CC_SORT,       /* Set the sort                         */
                                          DS_CC_SORT, DS_CC_SORT,       /* Set the sort                         */
                                          DS_CC_SORT, DS_CC_SORT);      /* Set the sort                         */
      //Set button names
      EditSetText(IDCANCEL, "Close");
      EditSetText(IDOK, "Copy");
      //Clear global
      ArrayClear(currentholding);
      //Set CIK text if exists
      if (c_cik != "") {
        EditSetText(RO_CIK, c_cik);
        ro_reload_data();
        }
      return ERROR_NONE;
      }

Here we set up the list on the dialog. We are setting up six columns, so we need to set the positions, the headings, and the flags for each column. We change the text on the cancel button from “Cancel” to “Close”, and we change the “OK” button to say “Copy.” We clear the currentholding array so that if the script has been run before there is no chance of accidentally reading values that should not exist for the current CIK. Finally, if the CIK from our settings file exists, we set the CIK edit control to have the CIK in it, and then we call the ro_reload_data() function. This means that we will be running the equivalent of last weeks script before the dialog fully opens so that the data in the dialog is correct as soon as it opens up.


    int ro_reload_data() {
    
      string    test[];
      int       rc;
      string    s2;
    
      //Get text
      c_cik = EditGetText(RO_CIK);
      //Zerofill
      c_cik = FormatString("%010s", c_cik);
      //Set text with zeroes
      EditSetText(RO_CIK, c_cik); 
      //Lookup CIK
      test = EDGARLookupCIK(c_cik);
      rc = GetLastError();
      //Is CIK Valid?
      if (rc == ERROR_EOD) { 
        MessageBox('x', "CIK is not valid.");
        return ERROR_EOD; 
        }
      //Get cache
      cache = AddPaths(CACHE_BASE, c_cik);
      //create cache
      CreateFolders(cache);
      if (IsFolder(cache) == FALSE) {                                   
        AddMessage('x', "Unable to create document cache.");    
        return ERROR_CANCEL;
        }                                                                       
      populate_filings();
      //Parse through filings to find all transactions
      parse_filings();
      //Parse through transactions and holdings to find current number
      get_current_holdings();
      //Put current holdings in dialog
      ro_load_list();
      }

Here is our equivalent to last week’s main function. We get the value from the CIK from the Edit control and we pad the left of the string with zero’s until our string is ten characters long. We then we use the EDGARLookupCIK() function to see if the CIK is a valid CIK. If the return code from the lookup is an ERROR_EOD, the CIK is not valid, and we let the user know before we exit the function. If the CIK is valid, we check to see if the cache exists, create it if it does not exist, and then we go through our main workflow: get the filings from the SEC, put them into the cache, get the data out of them, and then get the holdings from there. Then we call the re_load_list() function, which will put our data into the list.


    int ro_load_list(){
      
      int       count;
      int       max;
      int       printed;
      string    s1;
      
      //Reset data control
      DataControlResetContent(RO_LIST);
      DataControlSetSortColumn(RO_LIST);
      
      count = 0;
      max = ArrayGetAxisDepth(s16holdings, AXIS_ROW);
      printed = 0;
      //Print out all of our current holdings
      while (count < max) {
        if (s16holdings[count][COL_NUMOWNED] != "0") {
          s1 =  s16holdings[count][COL_DND] + "\t";
          s1 += s16holdings[count][COL_TDATE] + "\t";
          s1 += s16holdings[count][COL_CIK] + "\t";
          s1 += s16holdings[count][COL_NAME] + "\t";
          s1 += s16holdings[count][COL_NUMOWNED] + "\t";
          s1 += s16holdings[count][COL_DORI];
          
          DataControlAddString(RO_LIST, s1);
          DataControlSetRowData(RO_LIST, printed++, count);
          }
        count++;
        }
      return ERROR_NONE;
      }

The sole purpose of this function is to refresh the list in our data control with the values from our holdings table. So the first thing that we have to do is reset the content and reset the column sorting. Next we get the number of holdings in our table and we go through the table row by row. If a holding has a number greater than zero we want to add the holding to the data sheet. We add the values to a string separated by tab characters, and then we add the string to the data control, followed by telling the data control that we are finished with that row. After the loop finishes all of the values have been added to the data sheet and will be shown on the dialog. The next thing to talk about then is the action function:


    int ro_action(int c_id, int c_action) {
      
      int       ix;
      
      //Refresh CIK
      if (c_id == RO_REFRESH) { 
        ro_reload_data();
        return ERROR_NONE;
        }
      //Double click posts OK
      if (c_id == RO_LIST) {
        if (c_action == DCN_DOUBLE_CLICK) {
          ix = DataControlGetSelectCount(RO_LIST);                      
          if (ix == 0) { return ERROR_NONE; }                           
          DialogPostOK();
          return ERROR_NONE;
          }
        }
      //Copy all button
      if (c_id == RO_HOLDINGCOPY) {
        copyAll = true;
        DialogPostOK();
        return ERROR_NONE;
        }
      return ERROR_NONE;
      }

Our action function is pretty simple as we only care about three things: if the refresh button is pressed, if the user double clicks on a list item, or if the “Copy All” button is pressed. If the refresh button is pressed we call the ro_reload_data() function, which will refresh the dialog with new data. If the list control is double clicked we first check to see if a value was clicked. If it was, we tell the dialog to post an IDOK value. This is the equivalent of clicking on the “OK” button, which will close the dialog. Finally, if the user clicks on the “Copy All” button, we set a global flag to be true and ask the dialog to post IDOK. Speaking of clicking on the “OK” button, we have one last new function to look at which gets called whenever we click the “OK” button:


    int ro_validate() {
      int       ix[][];
      int       count;
      int       iy;
      int       currow;
      
      //Check how many rows selected
      count = DataControlGetSelectCount(RO_LIST);
      //If copy all, get how many holdings total
      if (copyAll) {
        count = ArrayGetAxisDepth(s16holdings, AXIS_ROW);
        }
      //If not selected
      if (count == 0) {
        MessageBox('x', "Select at least one item in the holdings list to continue.");
        return ERROR_SOFT | RO_LIST;
        }
      //Go through all selected data
      ix = DataControlGetSelectList(RO_LIST);
      iy = 0;
      while (iy < count) {
        if(copyAll) {
          currow = iy;
          }
        else {
          currow = ix[iy][0];
          }
        //Put selected data into array
        currentholding[iy][COL_TORH] = s16holdings[currow][COL_TORH];
        currentholding[iy][COL_AORD] = s16holdings[currow][COL_AORD];
        currentholding[iy][COL_TDATE] = s16holdings[currow][COL_TDATE];
        currentholding[iy][COL_TTYPE] = s16holdings[currow][COL_TTYPE];
        currentholding[iy][COL_DORI] = s16holdings[currow][COL_DORI];
        currentholding[iy][COL_NUMTRANS] = s16holdings[currow][COL_NUMTRANS];
        currentholding[iy][COL_NUMOWNED] = s16holdings[currow][COL_NUMOWNED];
        currentholding[iy][COL_CIK] = s16holdings[currow][COL_CIK];
        currentholding[iy][COL_NAME] = s16holdings[currow][COL_NAME];
        currentholding[iy][COL_DND] = s16holdings[currow][COL_DND];
        iy++;
        }
      return ERROR_NONE;
      }

Validate checks to see if any values are selected before the dialog closes. We get the row selection from the Data Control and if the value is less than zero we know there is nothing selected. If that is the case, we throw a soft error, which stops the dialog from closing but does not stop the rest of the script from running. If there is a value selected we fill out the currentholding array with the values from the list and return. This means our main function will have access to these values when we come back to it. If the “Copy All” button was pressed, however, we want to make sure that validate copies all of the current holdings. So if copyAll is set, instead of checking to see if any values are selected, we go through every single value and copy it into the currentholding array.


The last change to talk about is a small change to our addtransaction() and addholding() functions. As mentioned earlier we needed to store whether or not a transaction was a derivative or non-derivative, so we have added a small section to each function:


      //If non-derivative
      if (parser == "</nonDerivativeTransaction>") {
        dnd = "Non-Derivative";
        }
      else {
        dnd = "Derivative";
        }

This variable is then put into the csv of transactions in its own column, COL_DND. This is the excerpt from the transaction function, but the code changes for the holding function looks the same except we compare parser against </nonDerivativeHolding> instead.


We have done it. We took an idea (creating a script that scrapes Section 16 holdings from the SEC’s website) and we have made it a reality. Each week we broke down an individual portion of the script and conquered the entire thing from start to finish, even though the initial task sounds difficult. In the end, we have even added the functionality to a dialog in order for users to be able to do several CIK’s and get the data in a form that is easy to read and understand. There are more improvements that could be made, such as additional validation and functionality with the data now that we have it, but this was a good example of taking a large task and breaking it down into smaller, more manageable, portions. The entire script, resource and all, is available above.


 


Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013.

Additional Resources

Novaworks’ Legato Resources

Legato Script Developers LinkedIn Group

Primer: An Introduction to Legato 


Posted by
Joshua Kwiatkowski
in Development at 17:47
Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)
No comments
Add Comment
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
   
 

Quicksearch

Categories

  • XML Accounting
  • XML AICPA News
  • XML FASB News
  • XML GASB News
  • XML IASB News
  • XML Development
  • XML Events
  • XML FERC
  • XML eForms News
  • XML FERC Filing Help
  • XML Filing Technology
  • XML Information Technology
  • XML Investor Education
  • XML MSRB
  • XML EMMA News
  • XML FDTA
  • XML MSRB Filing Help
  • XML Novaworks News
  • XML GoFiler Online Updates
  • XML GoFiler Updates
  • XML XBRLworks Updates
  • XML SEC
  • XML Corporation Finance
  • XML DERA
  • XML EDGAR News
  • XML Investment Management
  • XML SEC Filing Help
  • XML XBRL
  • XML Data Quality Committee
  • XML GRIP Taxonomy
  • XML IFRS Taxonomy
  • XML US GAAP Taxonomy

Calendar

Back May '25 Forward
Mo Tu We Th Fr Sa Su
Sunday, May 18. 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Feeds

  • XML
Sign Up Now
Get SEC news articles and blog posts delivered monthly to your inbox!
Based on the s9y Bulletproof template framework

Compliance

  • FERC
  • EDGAR
  • EMMA

Software

  • GoFiler Suite
  • SEC Exhibit Explorer
  • SEC Extractor
  • XBRLworks
  • Legato Scripting

Company

  • About Novaworks
  • News
  • Site Map
  • Support

Follow Us:

  • LinkedIn
  • YouTube
  • RSS
  • Newsletter
  • © 2024 Novaworks, LLC
  • Privacy
  • Terms of Use
  • Trademarks and Patents
  • Contact Us