Example #1
0
/**** Global functions definitions.   ****/
TA_RetCode TA_GetHistoryDataFromWeb( TA_DataSourceHandle *handle,
                                     TA_CategoryHandle   *categoryHandle,
                                     TA_SymbolHandle     *symbolHandle,
                                     TA_Period            period,
                                     const TA_Timestamp  *start,
                                     const TA_Timestamp  *end,
                                     TA_Field             fieldToAlloc,
                                     TA_ParamForAddData  *paramForAddData )
{
   TA_PROLOG

   TA_RetCode retCode;
   TA_StringCache *stringCache;
   TA_String *yahooName;
   TA_WebPage *webPage;
   TA_PrivateYahooHandle *yahooHandle;
   TA_DecodingParam localDecodingParam;
   TA_DecodingParam directYahooDecodingParam;
   const TA_DecodingParam *decodingParam;
   TA_FileHandle *fileHandle;
   TA_ReadOpInfo *readOpInfo;
   TA_UIRSuffixParsing suffixParsing;
   TA_Timestamp firstBarTimestamp, lastBarTimestamp, prevEndDate;
   TA_InfoFromAddedData infoFromAddedData;
   TA_DayOfWeek dayOfWeek;
   TA_Timestamp curAdjustYear, lastAdjustYear;  
   const char *overideServerAddr;

   int nbEstimateBar;
   int nbField;
   unsigned int nbBarAdded, nbTotalBarAdded;
   int again, firstTime, nbBatch;
   int zeroBarAddedAttempt;
   int doAdjustment;

   TA_TRACE_BEGIN( TA_GetHistoryDataFromWeb );

   /* Initialize some local variables. */
   stringCache   = TA_GetGlobalStringCache();
   yahooHandle   = (TA_PrivateYahooHandle *)handle->opaqueData;
   readOpInfo    = NULL;
   nbEstimateBar = 0;

   TA_ASSERT( categoryHandle != NULL );
   TA_ASSERT( symbolHandle != NULL );
   TA_ASSERT( categoryHandle->string != NULL );
   TA_ASSERT( symbolHandle->string != NULL );

   retCode = TA_BAD_PARAM;

   /* Set the initial first/last timestamp */
   if( start )
      TA_TimestampCopy( &firstBarTimestamp, start );
   else
      TA_SetDate( 1950, 1, 1, &firstBarTimestamp );

   if( end )
      TA_TimestampCopy( &lastBarTimestamp, end );
   else
      TA_SetDateNow( &lastBarTimestamp );

   /* Time component is not important for Yahoo! but all end of day
    * price bar use 00:00:00, so do the same here.
    */
   TA_SetTime( 0, 0, 0, &firstBarTimestamp );
   TA_SetTime( 0, 0, 0, &lastBarTimestamp );

   /* Make sure that lastBarTimestamp is a week-day. */
   dayOfWeek = TA_GetDayOfTheWeek( &lastBarTimestamp );
   if( (dayOfWeek == TA_SUNDAY) || (dayOfWeek == TA_SATURDAY) )
      TA_PrevWeekday( &lastBarTimestamp );

   TA_ASSERT( yahooHandle != NULL );

   if( yahooHandle->param->id == TA_YAHOO_ONE_SYMBOL )
   {
      /* User specified Yahoo! name. */
      yahooName = TA_StringDup(stringCache,yahooHandle->webSiteSymbol);
      TA_ASSERT( yahooName != NULL );
   }
   else
   {
      /* Map the TA-Lib name into the Yahoo! name. */
      retCode = TA_AllocStringFromLibName( categoryHandle->string,
                                           symbolHandle->string,
                                           &yahooName );  
      if( retCode != TA_SUCCESS )
      {
         TA_TRACE_RETURN( retCode );
      }

      TA_ASSERT( yahooName != NULL );
   }

   /* Check if the user did overide the server address in the location parameter. 
    * (as needed, convert from TA_String to char *)
    */
   if( yahooHandle->userSpecifiedServer )
      overideServerAddr = TA_StringToChar(yahooHandle->userSpecifiedServer);
   else
      overideServerAddr = NULL;
   
   /* Get the decoding parameter for the CSV web page. */
   if( yahooHandle->param->id == TA_YAHOO_ONE_SYMBOL )
   {
      retCode = TA_YahooIdxDataDecoding( yahooHandle->webSiteCountry,
                                         TA_YAHOOIDX_CSV_PAGE,
                                         &directYahooDecodingParam );
      if( retCode != TA_SUCCESS )
      {
         TA_StringFree( stringCache, yahooName );
         TA_TRACE_RETURN( retCode );
      }
      decodingParam = &directYahooDecodingParam;
   }
   else
   {
      decodingParam = TA_YahooIdxDecodingParam( yahooHandle->index, TA_YAHOOIDX_CSV_PAGE );
      if( !decodingParam )
      {
         TA_StringFree( stringCache, yahooName );
         TA_TRACE_RETURN( TA_INTERNAL_ERROR(103) );
      }
   }

   localDecodingParam = *decodingParam;

   /* Check if split/value adjustment are necessary. */
   if( (yahooHandle->param->flags & TA_DO_NOT_SPLIT_ADJUST) &&
       (yahooHandle->param->flags & TA_DO_NOT_VALUE_ADJUST) )
   {
      doAdjustment = 0;
   }
   else
      doAdjustment = 1;


   /* Parse the uirSuffix so the start/end date can be changed. */
   if( !setUIRSuffixParsing( decodingParam->uirSuffix, &suffixParsing ) )
   {
      /* This should never happen unless the
       * Yahoo! index protocol has been broken.
       */
      /* Clean-up and exit */
      TA_StringFree( stringCache, yahooName );
      TA_TRACE_RETURN( TA_INTERNAL_ERROR(104) );
   }

   /* Use a local copy of the decoding param. 
    * This is because the uirSuffix is replaced with
    * an allocated buffer (so the date field can be
    * manipulated).
    */

   /* Replace the uirSuffix with a large local buffer. */
   localDecodingParam.uirSuffix = TA_Malloc( suffixParsing.maxTotalLength );
   if( !localDecodingParam.uirSuffix )
   {
      /* Clean-up and exit */
      TA_StringFree( stringCache, yahooName );
      TA_TRACE_RETURN( TA_ALLOC_ERR );
   }

   /* Change the dates in the uirSuffix. */
   buildUIRSuffix( &suffixParsing,
                   &firstBarTimestamp, &lastBarTimestamp,
                   (char *)localDecodingParam.uirSuffix );

   /* nbBatch is a safety net to make sure that
    * TA-Lib won't stay forever in the while loop
    * in case Yahoo! changes their protocol.
    */
   nbBatch = 0; 

   /* Sometime Yahoo! return an empty csv file. Make
    * multiple attempts in that case.
    */
   zeroBarAddedAttempt = 0;

   again = 1;
   firstTime = 1;
   nbTotalBarAdded = 0;
   while( again && (++nbBatch < 100) && (zeroBarAddedAttempt < 10) )
   {    
      retCode = TA_DriverShouldContinue(paramForAddData);
      if( retCode != TA_SUCCESS )
      {
         TA_StringFree( stringCache, yahooName );
         TA_Free( (char *)localDecodingParam.uirSuffix );
         #if !defined( TA_SINGLE_THREAD )   
            if( readOpInfo )
               TA_ReadOpInfoFree( readOpInfo );
         #endif
         TA_TRACE_RETURN( retCode );
      }

      if( TA_TimestampLess(&lastBarTimestamp,&firstBarTimestamp) )
      {
          /* Get out of this loop if all the requested data
           * has been retreived already.
           */
         again = 0;
         break;
      }   

      retCode = TA_WebPageAllocFromYahooName( &localDecodingParam,
                                              TA_StringToChar(yahooName),
                                              overideServerAddr,
                                              &webPage, paramForAddData );
           
      if( retCode != TA_SUCCESS )
      {
         TA_StringFree( stringCache, yahooName );
         TA_Free( (char *)localDecodingParam.uirSuffix );
         #if !defined( TA_SINGLE_THREAD )   
            if( readOpInfo )
               TA_ReadOpInfoFree( readOpInfo );
         #endif
         TA_TRACE_RETURN( retCode );
      }

      /* Disguise the webPage stream into a "file". That way the speed
       * optimized ASCII decoder can be re-used (TA_ReadOp stuff).
       */
      retCode = TA_FileSeqOpenFromStream( webPage->content, &fileHandle );
      if( retCode != TA_SUCCESS )
      {
         /* Clean-up and exit */
         TA_StringFree( stringCache, yahooName );
         TA_WebPageFree( webPage );
         TA_Free( (char *)localDecodingParam.uirSuffix );
         #if !defined( TA_SINGLE_THREAD )   
            if( readOpInfo )
               TA_ReadOpInfoFree( readOpInfo );
         #endif
         TA_TRACE_RETURN( retCode );
      }

      if( firstTime )
      {
         /* Make assumption of the data provided
          * base on the number of fields in the CSV file.
          */
         nbField = nbCommaField( webPage->content );
         switch( nbField )
         {
         case 2:
            readOpInfo = yahooHandle->readOp2Fields;
            break;
         case 5:
            readOpInfo = yahooHandle->readOp5Fields;
            break;
         default:
            readOpInfo = yahooHandle->readOp6Fields;
         }

         #if !defined( TA_SINGLE_THREAD )   
            /* Must use a local copy if multi-threaded */
            retCode = TA_ReadOpInfoClone(&readOpInfo, readOpInfo);
            if( retCode != TA_SUCCESS )
            {
               /* Clean-up and exit */
               TA_StringFree( stringCache, yahooName );
               TA_WebPageFree( webPage );
               TA_Free( (char *)localDecodingParam.uirSuffix );
               TA_TRACE_RETURN( retCode );
            }
         #endif

         /* User asking for all the fields? */
         if( fieldToAlloc == TA_ALL )
         {
            switch( nbField )
            {
            case 2:
               fieldToAlloc = TA_CLOSE|TA_TIMESTAMP;
               break;
            case 5:
               fieldToAlloc = TA_OPEN|TA_HIGH|TA_LOW|TA_CLOSE|TA_TIMESTAMP;
               break;
            default:
               fieldToAlloc = TA_OPEN|TA_HIGH|TA_LOW|TA_CLOSE|TA_VOLUME|TA_TIMESTAMP;
            }
         }

         /* Optimize the read op for the requested data. */
         retCode = TA_ReadOp_Optimize( readOpInfo,
                                       period,
                                       fieldToAlloc );
         if( retCode != TA_SUCCESS )
         {
            /* Clean-up and exit */
            TA_StringFree( stringCache, yahooName );
            TA_WebPageFree( webPage );
            TA_Free( (char *)localDecodingParam.uirSuffix );
            #if !defined( TA_SINGLE_THREAD )   
               if( readOpInfo )
                  TA_ReadOpInfoFree( readOpInfo );
            #endif
            TA_TRACE_RETURN( retCode );
         }

         /* Make an estimation of the number of price bar. */
         nbEstimateBar  = TA_StreamCountChar( webPage->content, '\n' ) + 1;
         if( nbEstimateBar < 100 )
            nbEstimateBar = 100;
      }

      /* Interpret the CSV data. */
      retCode = TA_ReadOp_Do( fileHandle,                           
                              readOpInfo,
                              period, &firstBarTimestamp, &lastBarTimestamp,
                              nbEstimateBar, fieldToAlloc,
                              paramForAddData,
                              &nbBarAdded );

      TA_FileSeqClose( fileHandle );
      TA_WebPageFree( webPage );

      nbTotalBarAdded += nbBarAdded;

      if( retCode != TA_SUCCESS )
      {
         /* Clean-up and exit */
         TA_StringFree( stringCache, yahooName );
         TA_Free( (char *)localDecodingParam.uirSuffix );
         #if !defined( TA_SINGLE_THREAD )   
            if( readOpInfo )
               TA_ReadOpInfoFree( readOpInfo );
         #endif
         TA_TRACE_RETURN( retCode );
      }

      /* Yahoo! does not always return all the data it could, up to
       * the requested end date. It is important to detect these occurence
       * and cancel the usage of all data accumulated up to now. 
       */      
      TA_GetInfoFromAddedData( paramForAddData, &infoFromAddedData );
      if( infoFromAddedData.barAddedSinceLastCall )
      {
         /* Do some more checking by considering holidays, week-end etc... */
         if( !isGapAcceptable(&infoFromAddedData.highestTimestampAddedSinceLastCall, &lastBarTimestamp) )
         {
            /* Clean-up and exit */
            TA_StringFree( stringCache, yahooName );
            TA_Free( (char *)localDecodingParam.uirSuffix );
            #if !defined( TA_SINGLE_THREAD )   
               if( readOpInfo )
                  TA_ReadOpInfoFree( readOpInfo );
            #endif
            TA_TRACE_RETURN( TA_DATA_GAP );
         }
         
         TA_TimestampCopy( &lastBarTimestamp, &infoFromAddedData.lowestTimestamp );
      }

      #if DEBUG_PRINTF
      printf( "NB BAR ADDED=%d, TOTAL=%d\n", nbBarAdded, nbTotalBarAdded );
      #endif

      /* Verify if more data should be processed. 
       * Yahoo! sometimes slice their data, in 
       * batch of 200 price bars. 
       */
      if( firstTime && (nbBarAdded > 200) )
      {
         again = 0; /* Assume all the data extracted... exit the loop. */
      }
      else if( nbBarAdded == 0 )
      {
         /* Make multiple attempts when retreiving data succeed,
          * but somehow there is zero bar returned. 
          *
          * Sometimes this might be correct when there is truly no
          * more data available, so choosing an algorithm before
          * giving up is a comprimise between reliability and
          * usability. The data source is free... and you get
          * what you pay for after all ;)
          */
         if( (nbTotalBarAdded < 1000) && (zeroBarAddedAttempt >= 1) && (zeroBarAddedAttempt < 7) )
         {
            /* I did choose to add a delay when insufficient total data is returned. When
             * there is already ~5 years of data, most likely there is "Zero" returned
             * because there is NO more data available, so just do the retry without delay.
             */
            TA_Sleep(zeroBarAddedAttempt*2);
         }

         #if DEBUG_PRINTF
            printf( "Retry %d", zeroBarAddedAttempt );
         #endif

         zeroBarAddedAttempt++;
      }
      else
      {
         zeroBarAddedAttempt = 0;

         if( TA_TimestampEqual( &lastBarTimestamp, &prevEndDate ) )
         {
            /* prevEndDate is a "safety net" to
             * exit the loop early in case Yahoo! starts
             * to return always the same batch of data.
             * Just ignore the repetitive data and exit.
             */
            TA_Free( (char *)localDecodingParam.uirSuffix );
            TA_StringFree( stringCache, yahooName );
            #if !defined( TA_SINGLE_THREAD )   
               if( readOpInfo )
                  TA_ReadOpInfoFree( readOpInfo );
            #endif
            TA_TRACE_RETURN( TA_SUCCESS );
         }
         TA_TimestampCopy( &prevEndDate, &lastBarTimestamp );

         /* Request the data up to the day BEFORE
          * the last batch of data received.
          */
         TA_PrevDay( &lastBarTimestamp );

         /* Make sure that lastBarTimestamp is a week-day. */
         dayOfWeek = TA_GetDayOfTheWeek( &lastBarTimestamp );
         if( (dayOfWeek == TA_SUNDAY) || (dayOfWeek == TA_SATURDAY) )
            TA_PrevWeekday( &lastBarTimestamp );

         /* Change the dates in the uirSuffix. */
         buildUIRSuffix( &suffixParsing,
                         &firstBarTimestamp, &lastBarTimestamp,
                         (char *)localDecodingParam.uirSuffix );

         /* From that point, data is expected to be most likely
          * sent in batch of 200.
          */
         nbEstimateBar = 200;
      }
      
      firstTime = 0;
   }

   /* Get rid of some memory not used anymore. */
   TA_Free( (char *)localDecodingParam.uirSuffix );
   #if !defined( TA_SINGLE_THREAD )   
      if( readOpInfo )
         TA_ReadOpInfoFree( readOpInfo );
   #endif

   /* If adjusted data is requested, use splits and dividend info from Yahoo!. */
   if( doAdjustment && (nbTotalBarAdded >= 1) )
   {
      /* Get the decoding parameter for the adjustment page. */
      if( yahooHandle->param->id == TA_YAHOO_ONE_SYMBOL )
      {
         retCode = TA_YahooIdxDataDecoding( yahooHandle->webSiteCountry,
                                            TA_YAHOOIDX_ADJUSTMENT,
                                            &directYahooDecodingParam );
         if( retCode != TA_SUCCESS )
         {
            TA_StringFree( stringCache, yahooName );
            TA_TRACE_RETURN( retCode );
         }
         decodingParam = &directYahooDecodingParam;
      }
      else
      {
         decodingParam = TA_YahooIdxDecodingParam( yahooHandle->index, TA_YAHOOIDX_ADJUSTMENT );
         if( !decodingParam )
         {
            TA_StringFree( stringCache, yahooName );
            TA_TRACE_RETURN( TA_INTERNAL_ERROR(140) );
         }
      }
      localDecodingParam = *decodingParam;

      if( !setUIRSuffixParsing( decodingParam->uirSuffix, &suffixParsing ) )
      {
         /* This should never happen unless the
          * Yahoo! index protocol has been broken.
          */
         /* Clean-up and exit */
         TA_StringFree( stringCache, yahooName );
         TA_TRACE_RETURN( TA_INTERNAL_ERROR(141) );
      }

      /* Use a local copy of the decoding param. 
       * This is because the uirSuffix is replaced with
       * an allocated buffer (so the date field can be
       * manipulated).
       */

      /* Replace the uirSuffix with a large local buffer. */
      localDecodingParam.uirSuffix = TA_Malloc( suffixParsing.maxTotalLength );
      if( !localDecodingParam.uirSuffix )
      {
         /* Clean-up and exit */
         TA_StringFree( stringCache, yahooName );
         TA_TRACE_RETURN( TA_ALLOC_ERR );
      }


      /* curAdjustYear indicates for which year the download is
       * taking place.
       */
      TA_SetDefault( &curAdjustYear );
      TA_SetDateNow( &curAdjustYear );

      /* Identify the oldest year for which data was downloaded */
      TA_GetInfoFromAddedData( paramForAddData, &infoFromAddedData );
      TA_TimestampCopy( &lastAdjustYear, &infoFromAddedData.lowestTimestamp );
      TA_PrevYear( &lastAdjustYear ); /* Get one more year to be on the safe side. */

      while( TA_TimestampLess( &lastAdjustYear, &curAdjustYear ) )
      {
         /* Set prevEndDate to two years earlier. */
         TA_TimestampCopy( &prevEndDate, &curAdjustYear );
         TA_PrevYear( &prevEndDate );
         TA_PrevYear( &prevEndDate );
         
         /* Change the dates in the uirSuffix. */
         TA_SetDate( TA_GetYear(&curAdjustYear), 12, 31, &curAdjustYear );
         TA_SetDate( TA_GetYear(&prevEndDate), 1, 1, &prevEndDate );
         buildUIRSuffix( &suffixParsing,
                         &prevEndDate, &curAdjustYear,
                         (char *)localDecodingParam.uirSuffix );

         retCode = doAdjustments( &localDecodingParam, yahooName,
                                  yahooHandle, NULL, paramForAddData );
         if( retCode != TA_SUCCESS )
         {
            /* Clean-up and exit */
            TA_StringFree( stringCache, yahooName );
            TA_Free( (char *)localDecodingParam.uirSuffix );
            TA_TRACE_RETURN( retCode );
         }

         /* Move 3 years earlier. */
         TA_PrevYear( &prevEndDate );
         TA_TimestampCopy( &curAdjustYear, &prevEndDate );
      }

      /* Clean-up what was allocated for the adjustment logic. */
      TA_Free( (char *)localDecodingParam.uirSuffix );
   }

   /* Clean-up and exit */
   TA_StringFree( stringCache, yahooName );
   TA_TRACE_RETURN( retCode );
}
Example #2
0
TA_RetCode TA_ReadOpInfoAlloc( const char *sourceInfo,
                               TA_ReadOpInfo **allocatedInfo,
                               unsigned int readOpFlags )
{
   TA_PROLOG
   TA_RetCode retCode;

   TA_ReadOp readOp;
   TA_ReadOp *arrayReadOp;
   TA_ReadOpInfo *newReadOpInfo;
   TA_Field fieldMask, fieldProvided;
   unsigned int timeframeIdx;
   unsigned int intraDayIncPeriod;
   TA_TokenId   intraDayIncToken;
   TA_TokenId   tempToken;
   unsigned int tempInt;
   unsigned int period;
   unsigned int errorOccurred;

   const char *pos;
   unsigned int inField;
   unsigned int nbField;
   unsigned int nbCharInField;
   unsigned int skipNonDigitLine;
   const char *ptrFirstCarInField;

   unsigned char localBuf[10];
   unsigned int bufIdx, opIdx, i;

   register unsigned int flagSet;
   register TA_ReadOp *ptrReadOp;
   
   TA_TRACE_BEGIN(  TA_BuildArrayReadOp );

   newReadOpInfo = (TA_ReadOpInfo *)TA_Malloc( sizeof( TA_ReadOpInfo ) );

   /* These variables are resolved within this function. */
   memset( newReadOpInfo, 0, sizeof( TA_ReadOpInfo ) );

   /* At this point, TA_ReadOpInfoFree can be safely called. */

   /* Keep track of some user provided parameter. */
   newReadOpInfo->sourceInfo = sourceInfo;
   newReadOpInfo->readOpFlags = readOpFlags;

   /* Initialize some defaults. */
   newReadOpInfo->openInterestMult = 100;
   newReadOpInfo->volumeMult       = 100;

   nbField = 0;
   intraDayIncPeriod = 0;
   intraDayIncToken = TA_TOK_END;

   pos = newReadOpInfo->sourceInfo;
   if( !pos || (*pos == '\0') )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_MISSING_FIELD );
   }

   /* Find how many fields are defined and check some syntax
    * at the same time.
    */
   if( *pos != '[' )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_INVALID_FIELD );
   }

   inField = 0;
   nbCharInField = 0;
   skipNonDigitLine = 0;
   ptrFirstCarInField = NULL;
   while( *pos != '\0' )
   {
      switch( *pos )
      {
      case '[':
         if( inField )
         {
            TA_ReadOpInfoFree( newReadOpInfo );
            TA_TRACE_RETURN( TA_INVALID_FIELD );
         }
         inField = 1;
         break;
      case ']':
         if( (!inField) || (nbCharInField == 0) )
         {
            TA_ReadOpInfoFree( newReadOpInfo );
            TA_TRACE_RETURN( TA_INVALID_FIELD );
         }

         nbField++;

         /* Exclude fields not generating a TA_ReadOp.
          * For the time being that means only the -H and -NDL field.
          */
         if( nbCharInField >= 2 )
         {
            TA_ASSERT( ptrFirstCarInField != NULL );
            if( ptrFirstCarInField[0] == '-' ) 
            {
               if( toupper(ptrFirstCarInField[1]) == 'H' )
                  nbField--;
               else if( (toupper(ptrFirstCarInField[1]) == 'N') &&
                        (toupper(ptrFirstCarInField[2]) == 'D') &&
                        (toupper(ptrFirstCarInField[3]) == 'L') )
               {
                  skipNonDigitLine = 1;
                  nbField--;
               }               
             }
         }

         inField = 0;
         nbCharInField = 0;
         ptrFirstCarInField = NULL;
         break;
      default:
         if( !inField )
         {
            TA_ReadOpInfoFree( newReadOpInfo );
            TA_TRACE_RETURN( TA_INVALID_FIELD );
         }

         if( nbCharInField == 0 )
            ptrFirstCarInField = pos;
         nbCharInField++;
         break;
      }

      pos++;
   }

   if( inField || *(pos-1) != ']' )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_INVALID_FIELD );
   }

   /* Build the TA_ReadOp array */
   arrayReadOp = (TA_ReadOp *)TA_Malloc( sizeof( TA_ReadOp ) * nbField );

   if( !arrayReadOp )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_ALLOC_ERR );
   }

   newReadOpInfo->arrayReadOp = arrayReadOp;

   pos = TA_StringToChar(newReadOpInfo->sourceInfo);

   bufIdx = 0;
   opIdx = 0;
   while( *pos != '\0' && (opIdx < nbField) )
   {
      switch( *pos )
      {
      case '[':
        break;

      case ']':
        localBuf[bufIdx] ='\0';
        bufIdx = 0;

        /* Identify the field and build the TA_ReadOp. */
        tempInt = 0;
        retCode = buildReadOp( newReadOpInfo,
                               (const char *)&localBuf[0],
                               &arrayReadOp[opIdx],
                               &tempToken, &tempInt );
        if( retCode != TA_SUCCESS )
        {
           TA_ReadOpInfoFree( newReadOpInfo );
           TA_TRACE_RETURN( retCode );
        }

        if( arrayReadOp[opIdx] != 0 )
        {
           /* Set the replace zero flag as needed */
           if( TA_IS_REPLACE_ZERO(readOpFlags) && TA_IS_REAL_CMD(arrayReadOp[opIdx]) )
           {
              TA_SET_REPLACE_ZERO(arrayReadOp[opIdx]);
           }

           /* Set the skipNonDigitLine flag as needed. */
           if( skipNonDigitLine == 1 )
           {
              TA_SET_SKIP_NDL_FLAG(arrayReadOp[opIdx]);
           }

           /* Ooof... this readOp is now all build! */
           opIdx++;
        }

        /* If this is a time token, make sure this
         * is not in contradiction with an already
         * specified increment.
         */
        if( intraDayIncPeriod )
        {
           errorOccurred = 0;
           switch( tempToken )
           {
           case TA_TOK_SEC:
           case TA_TOK_SS:
              if( (intraDayIncToken == TA_TOK_MIN) ||
                  (intraDayIncToken == TA_TOK_MN) )
                 errorOccurred = 1;
              /* no break */
           case TA_TOK_MIN:
           case TA_TOK_MN:
              if( (intraDayIncToken == TA_TOK_HOUR) ||
                  (intraDayIncToken == TA_TOK_HH) )
                 errorOccurred = 1;
              break;
           case TA_TOK_HOUR:
           case TA_TOK_HH:
              errorOccurred = 1;
              break;
           default:
              /* Do nothing */
              break;
           }

           if( errorOccurred )
           {
              TA_ReadOpInfoFree( newReadOpInfo );
              TA_TRACE_RETURN( TA_INVALID_FIELD );
           }
        }

        /* Check if a period increment is specified. */
        if( (tempInt != 0) && (tempInt != 1) )
        {
           if( intraDayIncPeriod != 0 )
           {
              TA_ReadOpInfoFree( newReadOpInfo );
              TA_TRACE_RETURN( TA_INVALID_FIELD );
           }

           intraDayIncPeriod = tempInt;
           intraDayIncToken  = tempToken;
        }
        break;

      default:
        if( bufIdx >= sizeof(localBuf)-1 )
        {
           TA_ReadOpInfoFree( newReadOpInfo );
           TA_TRACE_RETURN( TA_INVALID_FIELD );
        }

        localBuf[bufIdx++] = *pos;
        break;
      }

      pos++;
   }

   if( opIdx != nbField )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_INTERNAL_ERROR(89) );
   }

   arrayReadOp[opIdx-1] |= TA_CMD_LAST_FLAG;

   /* Build the mask representing the fields provided. */
   fieldProvided = 0;
   timeframeIdx = TA_INTEGER_ARRAY_SIZE;

   for( opIdx=0; opIdx < nbField; opIdx++ )
   {
      readOp = arrayReadOp[opIdx];

      TA_ASSERT( readOp != 0 ); /* Parano test */

      if( !TA_IS_PERMANENT_SKIP_SET(readOp) )
      {
         /* Make sure this field was not specified twice. */
         for( i=opIdx+1; i < nbField; i++ )
         {
            if( (TA_IS_REAL_CMD(readOp) && TA_IS_REAL_CMD(arrayReadOp[i])) ||
                (TA_IS_INTEGER_CMD(readOp) && TA_IS_INTEGER_CMD(arrayReadOp[i])) )
            {
               if( (TA_GET_IDX(readOp) == TA_GET_IDX(arrayReadOp[i])) &&
                   !TA_IS_PERMANENT_SKIP_SET(arrayReadOp[i]) )
               {
                  TA_ReadOpInfoFree( newReadOpInfo );
                  TA_TRACE_RETURN( TA_REDUNDANT_FIELD );
               }
            }
         }

         /* Parano test: Double-check redundant field in a different way. */
         fieldMask = TA_ReadOpToField( readOp );
         TA_ASSERT( fieldMask != 0 );
         if( !(fieldMask & TA_TIMESTAMP) && (fieldProvided & fieldMask) )
         {
            TA_ReadOpInfoFree( newReadOpInfo );
            TA_TRACE_RETURN( TA_REDUNDANT_FIELD );
         }

         /* Set the field. */
         fieldProvided |= fieldMask;

         /* Keep track of the smallest granularity of the timestamp. */
         if( fieldMask & TA_TIMESTAMP )
         {
            if( (timeframeIdx == TA_INTEGER_ARRAY_SIZE) ||
                (TA_GET_IDX(readOp) < timeframeIdx) )
               timeframeIdx = TA_GET_IDX(readOp);
         }
      }
   }


   /* No date/time reference provided!? This is considered an error
    * in the current implementation.
    */
   if( timeframeIdx == TA_INTEGER_ARRAY_SIZE )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_MISSING_DATE_OR_TIME_FIELD );
   }

   /* Determine at which point the timestamp is completed. */
   flagSet = 0;
   for( opIdx=nbField; opIdx > 0; opIdx-- )
   {
      ptrReadOp = &arrayReadOp[opIdx-1];
      readOp = *ptrReadOp;

      if( !flagSet                          && 
          TA_IS_INTEGER_CMD(readOp)         && 
          (TA_GET_IDX(readOp)<=TA_YEAR_IDX) && 
          !TA_IS_PERMANENT_SKIP_SET(readOp) )
      {
         TA_SET_TIMESTAMP_COMPLETE(*ptrReadOp);
         flagSet = 1;
      }
      else
      {
         TA_CLR_TIMESTAMP_COMPLETE(*ptrReadOp);
      }
   }

   /* Validate and identify the period. */
   period = 0;
   if( intraDayIncPeriod )
   {
      errorOccurred = 0;
      switch( timeframeIdx )
      {
      case TA_YEAR_IDX:
      case TA_MONTH_IDX:
      case TA_DAY_IDX:
         errorOccurred = 1;
         break;
      case TA_HOUR_IDX:
         if( (intraDayIncPeriod < TA_1HOUR) || (intraDayIncPeriod >= TA_DAILY) )
            errorOccurred = 1;
         break;
      case TA_MIN_IDX:
         if( (intraDayIncPeriod < TA_1MIN) || (intraDayIncPeriod >= TA_1HOUR) )
            errorOccurred = 1;
         break;
      case TA_SEC_IDX:
         if( (intraDayIncPeriod < TA_1SEC) || (intraDayIncPeriod >= TA_1MIN) )
            errorOccurred = 1;
         break;
      default:
         TA_ReadOpInfoFree( newReadOpInfo );
         TA_FATAL(  NULL, timeframeIdx, fieldProvided );
      }

      if( errorOccurred )
      {
         TA_ReadOpInfoFree( newReadOpInfo );
         TA_TRACE_RETURN( TA_INVALID_FIELD );
      }
            
      period = intraDayIncPeriod;
   }
   else
   {
      switch( timeframeIdx )
      {
      case TA_YEAR_IDX:
         period = TA_YEARLY;
         break;
      case TA_MONTH_IDX:
         period = TA_MONTHLY;
         break;
      case TA_DAY_IDX:
         period = TA_DAILY;
         break;
      case TA_HOUR_IDX:
         period = TA_1HOUR;
         break;
      case TA_MIN_IDX:
         period = TA_1MIN;
         break;
      case TA_SEC_IDX:
         period = TA_1SEC;
         break;
      default:
         TA_FATAL(  NULL, timeframeIdx, fieldProvided );
      }
   }

   /* A last check... */
   if( period == 0 )
   {
      TA_ReadOpInfoFree( newReadOpInfo );
      TA_TRACE_RETURN( TA_INVALID_FIELD );
   }
         
   /* Everything is fine, let's return the information. */
   newReadOpInfo->arrayReadOp = arrayReadOp;
   newReadOpInfo->fieldProvided = fieldProvided;
   newReadOpInfo->nbReadOp = nbField;
   newReadOpInfo->period = period;

   *allocatedInfo = newReadOpInfo;

   TA_TRACE_RETURN( TA_SUCCESS );
}