TA_RetCode TA_PMAlloc( const TA_Timestamp *startDate, const TA_Timestamp *endDate, TA_Real initialCapital, TA_PM **allocatedPM ) { TA_PM *pm; TA_PMPriv *pmPriv; unsigned int delta; TA_RetCode retCode; /* Check all the parameters. */ if( !allocatedPM ) return TA_BAD_PARAM; *allocatedPM = NULL; if( !startDate || !endDate ) return TA_BAD_PARAM; if( TA_TimestampValidate( startDate ) ) return TA_BAD_START_DATE; if( TA_TimestampValidate( endDate ) || TA_TimestampGreater( startDate, endDate ) ) return TA_BAD_END_DATE; /* To keep things simple, it is assumed that * the requested date range contains at least * one weekday. */ retCode = TA_TimestampDeltaWeekday( startDate, endDate, &delta ); if( retCode != TA_SUCCESS ) return retCode; if( delta <= 0 ) return TA_NO_WEEKDAY_IN_DATE_RANGE; /* Allocate the public and private structure. */ pm = TA_Malloc( sizeof( TA_PM ) + sizeof( TA_PMPriv ) ); if( !pm ) return TA_ALLOC_ERR; memset( pm, 0, sizeof( TA_PM ) + sizeof( TA_PMPriv ) ); pmPriv = (TA_PMPriv *)(((char *)pm)+sizeof(TA_PM)); pmPriv->magicNb = TA_PMPRIV_MAGIC_NB; pmPriv->initialCapital = initialCapital; pm->hiddenData = pmPriv; TA_ListInit( &pmPriv->tradeLogList ); /* TA_PMFree can be safely called from this point. */ TA_TimestampCopy( &pmPriv->endDate, endDate ); TA_TimestampCopy( &pmPriv->startDate, startDate ); /* Success, return the allocated data to the caller. */ *allocatedPM = pm; return TA_SUCCESS; }
static int parseDate( const char *str, TA_Timestamp *timestamp ) { TA_Timestamp temp; int day, month, year; TA_RetCode retCode; int found; static const char *monthStr[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; if( !timestamp || !str ) return 0; /* Parse day */ if( sscanf( str, "%d", &day ) != 1 ) return 0; /* Parse Month */ found = 0; month = 0; while( !found && (month < 12) ) { if( strstr(str, monthStr[month] ) ) found = 1; else month++; } if( !found ) return 0; /* Parse Year */ str = strchr( str, '-' ); if( !str || str[1] == '\0' ) return 0; str = strchr( str+1, '-' ); if( !str || str[1] == '\0' ) return 0; if( sscanf( str+1, "%d", &year ) != 1 ) return 0; /* Build timestamp */ TA_SetDefault( &temp ); retCode = TA_SetDate( year, month+1, day, &temp ); if( retCode != TA_SUCCESS ) return 0; retCode = TA_TimestampCopy( timestamp, &temp ); if( retCode != TA_SUCCESS ) return 0; return 1; }
/**** Global functions definitions. ****/ TA_RetCode TA_GetHistoryDataFromWeb( TA_Libc *libHandle, 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; const TA_DecodingParam *decodingParam; TA_FileHandle *fileHandle; TA_ReadOpInfo *readOpInfo; UIRSuffixParsing suffixParsing; TA_Timestamp firstBarTimestamp, lastBarTimestamp, prevEndDate; TA_InfoFromAddedData infoFromAddedData; TA_DayOfWeek dayOfWeek; int nbEstimateBar; int nbField; unsigned int nbBarAdded, nbTotalBarAdded; int again, firstTime, nbBatch; int zeroBarAddedAttempt; TA_TRACE_BEGIN( libHandle, TA_GetHistoryDataFromWeb ); /* Initialize some local variables. */ stringCache = TA_GetGlobalStringCache( libHandle ); yahooHandle = (TA_PrivateYahooHandle *)handle->opaqueData; readOpInfo = NULL; nbEstimateBar = 0; TA_ASSERT( libHandle, categoryHandle != NULL ); TA_ASSERT( libHandle, symbolHandle != NULL ); TA_ASSERT( libHandle, categoryHandle->string != NULL ); TA_ASSERT( libHandle, symbolHandle->string != NULL ); /* Set the initial first/last timestamp */ if( start ) TA_TimestampCopy( &firstBarTimestamp, start ); else { TA_SetDate( 1950, 1, 1, &firstBarTimestamp ); TA_SetTime( 0, 0, 0, &firstBarTimestamp ); } if( end ) TA_TimestampCopy( &lastBarTimestamp, end ); else { TA_SetDateNow( &lastBarTimestamp ); 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 ); /* Map the TA-Lib name into the Yahoo! name. */ retCode = TA_AllocStringFromLibName( libHandle, categoryHandle->string, symbolHandle->string, &yahooName ); if( retCode != TA_SUCCESS ) { TA_TRACE_RETURN( retCode ); } TA_ASSERT( libHandle, yahooName != NULL ); TA_ASSERT( libHandle, yahooHandle != NULL ); /* Get the decoding parameter for the CSV web page. */ decodingParam = TA_YahooIdxDecodingParam( yahooHandle->index, TA_YAHOOIDX_CVS_PAGE ); if( !decodingParam ) { TA_StringFree( stringCache, yahooName ); TA_TRACE_RETURN( TA_INTERNAL_ERROR(103) ); } /* 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). */ localDecodingParam = *decodingParam; /* 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) ); } /* Replace the uirSuffix with a large local buffer. */ localDecodingParam.uirSuffix = TA_Malloc( libHandle, 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) ) { 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( libHandle, &localDecodingParam, TA_StringToChar(yahooName), &webPage ); if( retCode != TA_SUCCESS ) { TA_StringFree( stringCache, yahooName ); TA_Free( libHandle, (char *)localDecodingParam.uirSuffix ); 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( libHandle, webPage->content, &fileHandle ); if( retCode != TA_SUCCESS ) { /* Clean-up and exit */ TA_StringFree( stringCache, yahooName ); TA_WebPageFree( webPage ); TA_Free( libHandle, (char *)localDecodingParam.uirSuffix ); 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; } /* 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( libHandle, readOpInfo, period, fieldToAlloc ); if( retCode != TA_SUCCESS ) { /* Clean-up and exit */ TA_StringFree( stringCache, yahooName ); TA_WebPageFree( webPage ); TA_Free( libHandle, (char *)localDecodingParam.uirSuffix ); 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( libHandle, fileHandle, readOpInfo, period, &firstBarTimestamp, &lastBarTimestamp, nbEstimateBar, fieldToAlloc, paramForAddData, &nbBarAdded ); TA_FileSeqClose( libHandle, fileHandle ); TA_WebPageFree( webPage ); nbTotalBarAdded += nbBarAdded; if( retCode != TA_SUCCESS ) { /* Clean-up and exit */ TA_StringFree( stringCache, yahooName ); TA_Free( libHandle, (char *)localDecodingParam.uirSuffix ); 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( libHandle, (char *)localDecodingParam.uirSuffix ); 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( libHandle, (char *)localDecodingParam.uirSuffix ); TA_StringFree( stringCache, yahooName ); 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; } /* Clean-up and exit */ TA_Free( libHandle, (char *)localDecodingParam.uirSuffix ); TA_StringFree( stringCache, yahooName ); TA_TRACE_RETURN( retCode ); }
TA_RetCode TA_SIMULATOR_GetHistoryData( 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_PrivateHandle *privateHandle; TA_RetCode retCode; TA_Timestamp *timestamp; TA_Real *open, *high, *low, *close; TA_Integer *volume; unsigned int i; (void)fieldToAlloc; (void)end; (void)start; (void)period; TA_TRACE_BEGIN( TA_SIMULATOR_GetHistoryData ); TA_ASSERT( handle != NULL ); privateHandle = (TA_PrivateHandle *)handle->opaqueData; TA_ASSERT( privateHandle != NULL ); TA_ASSERT( paramForAddData != NULL ); TA_ASSERT( categoryHandle != NULL ); TA_ASSERT( symbolHandle != NULL ); retCode = TA_INTERNAL_ERROR(98); /* Note: start/end index are currently ignored * in this data source. */ /* Identify the category. */ switch( (unsigned int)categoryHandle->opaqueData ) { case 0: /* This is TA_SIM_REF */ switch( (unsigned int)symbolHandle->opaqueData ) { case 0: timestamp = (TA_Timestamp *)NULL; open = high = low = close = (TA_Real *)NULL; volume = (TA_Integer *)NULL; #define TA_ALLOC_COPY( varName, varType, varSize) { \ varName = TA_Malloc( sizeof( varType ) * varSize ); \ if( !varName ) \ { \ FREE_IF_NOT_NULL( open ); \ FREE_IF_NOT_NULL( high ); \ FREE_IF_NOT_NULL( low ); \ FREE_IF_NOT_NULL( close ); \ FREE_IF_NOT_NULL( volume ); \ TA_TRACE_RETURN( TA_ALLOC_ERR ); \ } \ memcpy( varName, TA_SREF_##varName##_daily_ref_0_PRIV, sizeof( varType )*varSize ); } TA_ALLOC_COPY( open, TA_Real, TA_REF_DAILY_NB_BARS ); TA_ALLOC_COPY( high, TA_Real, TA_REF_DAILY_NB_BARS ); TA_ALLOC_COPY( low, TA_Real, TA_REF_DAILY_NB_BARS ); TA_ALLOC_COPY( close, TA_Real, TA_REF_DAILY_NB_BARS ); TA_ALLOC_COPY( volume, TA_Integer, TA_REF_DAILY_NB_BARS ); #undef TA_ALLOC_COPY /* Set the timestamp. */ timestamp = TA_Malloc( sizeof( TA_Timestamp ) * TA_REF_DAILY_NB_BARS ); if( !timestamp ) { TA_Free( open ); TA_Free( high ); TA_Free( low ); TA_Free( close ); TA_Free( volume ); TA_TRACE_RETURN( TA_ALLOC_ERR ); } for( i=0; i < TA_REF_DAILY_NB_BARS; i++ ) TA_TimestampCopy( ×tamp[i], &TA_SREF_timestamp_daily_ref_0_PRIV[i] ); retCode = TA_HistoryAddData( paramForAddData, TA_REF_DAILY_NB_BARS, TA_DAILY, timestamp, open, high, low, close, volume, NULL ); break; case 1: /* Allocate the rest. */ timestamp = (TA_Timestamp *)NULL; open = high = low = close = (TA_Real *)NULL; #define TA_ALLOC_COPY( varName, varType, varSize) { \ varName = TA_Malloc( sizeof( varType ) * varSize ); \ if( !varName ) \ { \ TA_Free( timestamp ); \ FREE_IF_NOT_NULL( open ); \ FREE_IF_NOT_NULL( high ); \ FREE_IF_NOT_NULL( low ); \ FREE_IF_NOT_NULL( close ); \ TA_TRACE_RETURN( TA_ALLOC_ERR ); \ } \ memcpy( varName, TA_SREF_##varName##_daily_ref_0_PRIV, sizeof( varType )*varSize ); } TA_ALLOC_COPY( open, TA_Real, 33 ); TA_ALLOC_COPY( high, TA_Real, 33 ); TA_ALLOC_COPY( low, TA_Real, 33 ); TA_ALLOC_COPY( close, TA_Real, 33 ); #undef TA_ALLOC_COPY /* Set the timestamp. */ timestamp = (TA_Timestamp *)TA_Malloc( sizeof( TA_Timestamp ) * 33 ); if( !timestamp ) { FREE_IF_NOT_NULL( open ); FREE_IF_NOT_NULL( high ); FREE_IF_NOT_NULL( low ); FREE_IF_NOT_NULL( close ); TA_TRACE_RETURN( TA_ALLOC_ERR ); } for( i=0; i < TA_REF_INTRA_NB_BARS; i++ ) TA_TimestampCopy( ×tamp[i], &TA_SREF_timestamp_intra_ref_0_PRIV[i] ); retCode = TA_HistoryAddData( paramForAddData, TA_REF_INTRA_NB_BARS, TA_1MIN*10, timestamp, open, high, low, close, NULL, NULL ); break; } break; case 1: /* This is TA_SIM_MRG */ retCode = addSimMrgData( privateHandle, paramForAddData ); break; } TA_TRACE_RETURN( retCode ); }
TA_RetCode TA_TradeLogAdd( TA_TradeLog *tradeLog, const TA_Transaction *newTransaction ) { TA_TradeLogPriv *tradeLogPriv; TA_Instrument *id; TA_Dict *theDict; TA_DataLog *dataLog; TA_TradeDictEntry *dictEntry; TA_StringCache *stringCache; TA_String *catString; TA_String *symString; const char *catCharPtr; const char *symCharPtr; TA_RetCode retCode; int quantity, entryTradeQuantity; TA_List *entryListToUse; TA_DataLog *entryTradeLog; TA_Real highestLow, highestHigh, lowestLow, lowestHigh; TA_Real entryPrice, tempReal; int i; retCode = TA_INTERNAL_ERROR(120); /* This function will transform the TA_Transaction into * an "entry" or multiple "trades" (because an exit can * be translated into multiple trade if there was multiple * entry point). */ if( !tradeLog || !newTransaction ) return TA_BAD_PARAM; /* Check that the TA_Transaction makes sense. */ if( (newTransaction->price <= 0.0) || (newTransaction->quantity <= 0) || (TA_TimestampValidate(&newTransaction->timestamp) != TA_SUCCESS) || (newTransaction->type >= TA_NB_TRADE_TYPE)) return TA_BAD_PARAM; /* Get access to the hidden data of the TA_TradeLog. */ tradeLogPriv = (TA_TradeLogPriv *)tradeLog->hiddenData; /* Make sure this is a valid object. */ if( !tradeLogPriv || (tradeLogPriv->magicNb != TA_TRADELOGPRIV_MAGIC_NB) ) return TA_BAD_OBJECT; /* Find the TA_TradeDictEntry corresponding to * the TA_Instrument. * * Use the dictionary corresponding to the type of * key of the TA_Instrument. * * If TA_Instrument is NULL, use the pre-allocated * default TA_TradeDictEntry. */ id = newTransaction->id; if( !id ) { dictEntry = &tradeLogPriv->defaultDictEntry; catCharPtr = NULL; symCharPtr = NULL; theDict = NULL; } else { catCharPtr = id->catString; symCharPtr = id->symString; if( catCharPtr ) { if( symCharPtr ) { theDict = tradeLogPriv->tradeDictCATSYM; dictEntry = TA_DictGetValue_S2( theDict, catCharPtr, symCharPtr ); } else { theDict = tradeLogPriv->tradeDictCAT; dictEntry = TA_DictGetValue_S( theDict, catCharPtr ); } } else if( symCharPtr ) { theDict = tradeLogPriv->tradeDictCAT; dictEntry = TA_DictGetValue_S( theDict, symCharPtr ); } else { theDict = tradeLogPriv->tradeDictUserKey; dictEntry = TA_DictGetValue_I( theDict, id->userKey ); } } if( !dictEntry ) { if( !theDict ) return TA_INTERNAL_ERROR(146); /* The TA_TradeDictEntry was not found, create it! */ dictEntry = TA_Malloc( sizeof( TA_TradeDictEntry ) ); if( !dictEntry ) return TA_ALLOC_ERR; memset( &dictEntry->id, 0, sizeof(TA_Instrument) ); TA_ListInit( &dictEntry->shortEntryPrivList ); TA_ListInit( &dictEntry->longEntryPrivList ); /* Add the dictEntry to the corresponding dictionary. */ stringCache = TA_GetGlobalStringCache(); if( catCharPtr ) { catString = TA_StringAlloc( stringCache, catCharPtr ); if( !catString ) { TA_Free( dictEntry ); return TA_ALLOC_ERR; } if( symCharPtr ) { symString = TA_StringAlloc( stringCache, symCharPtr ); if( !symString ) { TA_Free( dictEntry ); TA_StringFree( stringCache, catString ); return TA_ALLOC_ERR; } retCode = TA_DictAddPair_S2( theDict, catString, symString, dictEntry ); dictEntry->id.symString = TA_StringToChar(symString); } else retCode = TA_DictAddPair_S( theDict, catString, dictEntry ); dictEntry->id.catString = TA_StringToChar(catString); } else if( symCharPtr ) { symString = TA_StringAlloc( stringCache, symCharPtr ); if( !symString ) { TA_Free( dictEntry ); return TA_ALLOC_ERR; } retCode = TA_DictAddPair_S( theDict, symString, dictEntry ); dictEntry->id.symString = TA_StringToChar(symString); } else { retCode = TA_DictAddPair_I( theDict, id->userKey, dictEntry ); dictEntry->id.userKey = id->userKey; } /* Check the retCode of the TA_DictAddXXXXX function. */ if( retCode != TA_SUCCESS ) { TA_Free( dictEntry ); return TA_ALLOC_ERR; } } /* Identify the approriate list of entry. */ switch( newTransaction->type ) { case TA_LONG_ENTRY: case TA_LONG_EXIT: entryListToUse = &dictEntry->longEntryPrivList; break; case TA_SHORT_ENTRY: case TA_SHORT_EXIT: entryListToUse = &dictEntry->shortEntryPrivList; break; default: return TA_BAD_PARAM; } /* The sign of the quantity indicates if the * data log is a completed trade (+) or an * entry (-). */ switch( newTransaction->type ) { case TA_LONG_ENTRY: case TA_SHORT_ENTRY: /* Allocate a data log and add it to the list. */ dataLog = TA_AllocatorForDataLog_Alloc( &tradeLogPriv->allocator ); if( !dataLog ) return TA_ALLOC_ERR; dataLog->u.entry.quantity = -(newTransaction->quantity); dataLog->u.entry.entryPrice = newTransaction->price; TA_TimestampCopy( &dataLog->u.entry.entryTimestamp, &newTransaction->timestamp ); TA_ListNodeAddTail( entryListToUse, &dataLog->u.entry.node, dataLog ); break; case TA_LONG_EXIT: case TA_SHORT_EXIT: /* Invalidate cached calculation of this trade log. */ tradeLogPriv->flags &= ~TA_PMVALUECACHE_CALCULATED; /* Transform this transaction into one or * multiple trade(s). */ entryTradeLog = TA_ListRemoveHead( entryListToUse ); if( !entryTradeLog ) return TA_ENTRY_TRANSACTION_MISSING; quantity = newTransaction->quantity; while( quantity ) { entryTradeQuantity = -entryTradeLog->u.trade.quantity; if( entryTradeQuantity == quantity ) { /* This entry have exactly the right amount of * position for what needs to be closed. * Just transform the entry into a trade. */ entryTradeLog->u.trade.quantity = quantity; entryTradeLog->u.trade.id = id; TA_TimestampCopy( &entryTradeLog->u.trade.exitTimestamp, &newTransaction->timestamp ); /* Calculate the profit and make the entryPrice * negative if this is a short trade. * Both are multiplied by the quantity being * traded. */ entryPrice = entryTradeLog->u.entry.entryPrice; if( newTransaction->type == TA_LONG_EXIT ) { entryTradeLog->u.trade.profit = (newTransaction->price-entryPrice)*quantity; CALC_EXCURSION_LONG; } else { entryTradeLog->u.trade.profit = (entryPrice-newTransaction->price)*quantity; entryPrice = -entryPrice; CALC_EXCURSION_SHORT; } entryTradeLog->u.entry.entryPrice = entryPrice * quantity; return TA_SUCCESS; /* Done! */ } else if( entryTradeQuantity < quantity ) { /* This entry have less than the amount of * position that needs to be closed. * Just transform the entry into a trade * and move to the next entry. */ entryTradeLog->u.trade.quantity = entryTradeQuantity; quantity -= entryTradeQuantity; entryTradeLog->u.trade.id = id; TA_TimestampCopy( &entryTradeLog->u.trade.exitTimestamp, &newTransaction->timestamp ); /* Calculate the profit and make the entryPrice * negative if this is a short trade. * Both are multiplied by the quantity being * traded. */ entryPrice = entryTradeLog->u.trade.entryPrice; if( newTransaction->type == TA_LONG_EXIT ) { entryTradeLog->u.trade.profit = (newTransaction->price-entryPrice)*entryTradeQuantity; CALC_EXCURSION_LONG; } else { entryTradeLog->u.trade.profit = (entryPrice-newTransaction->price)*entryTradeQuantity; entryPrice = -entryPrice; CALC_EXCURSION_SHORT; } entryTradeLog->u.trade.entryPrice = entryPrice * entryTradeQuantity; /* Move to the next entry. If none available, that means there * was more "exit" than "entry" and this is considered an * error. */ entryTradeLog = TA_ListRemoveHead( entryListToUse ); if( !entryTradeLog ) return TA_ENTRY_TRANSACTION_MISSING; } else { /* This entry have more position than what the * exit requires, so the entry must be preserved. * Consequently, a new tradeLog must be allocated. */ dataLog = TA_AllocatorForDataLog_Alloc( &tradeLogPriv->allocator ); if( !dataLog ) return TA_ALLOC_ERR; TA_TimestampCopy( &dataLog->u.trade.entryTimestamp, &entryTradeLog->u.trade.entryTimestamp ); TA_TimestampCopy( &dataLog->u.trade.exitTimestamp, &newTransaction->timestamp ); dataLog->u.trade.quantity = quantity; entryPrice = entryTradeLog->u.trade.entryPrice; if( newTransaction->type == TA_LONG_EXIT ) { dataLog->u.trade.profit = (newTransaction->price-entryPrice)*quantity; CALC_EXCURSION_LONG; } else { dataLog->u.trade.profit = (entryPrice-newTransaction->price)*quantity; entryPrice = -entryPrice; CALC_EXCURSION_SHORT; } dataLog->u.trade.entryPrice = entryPrice*quantity; dataLog->u.trade.id = id; /* Adjust the entry and put it back for being process * again later. */ entryTradeLog->u.trade.quantity += quantity; TA_ListNodeAddHead( entryListToUse, &entryTradeLog->u.entry.node, entryTradeLog ); return TA_SUCCESS; /* Done! */ } } break; default: return TA_INTERNAL_ERROR(121); } return TA_SUCCESS; }
static ErrorNumber test_onetrade_only( TA_KEY_TYPE keyTypeTest, TA_TransactionType transactionType, unsigned int winningTrade ) { TA_RetCode retCode; TA_Instrument instrument; TA_Transaction transaction; TA_TradeLog *tradeLog; TA_PM *allocatedPM; ErrorNumber errorNumber; /* Allocate a TA_TradeLog. */ retCode = TA_TradeLogAlloc( &tradeLog ); if( (retCode != TA_SUCCESS) || (tradeLog == NULL) ) { printRetCode( retCode ); printf( "Failed: TA_TradeLogAlloc bad retCode! [%d]\n", retCode ); return TA_PM_EMPTY_TA_TRADE_LOG_TESTS_0; } /* Add the entry TA_Transaction */ switch( keyTypeTest ) { case TA_KEY_TYPE_INTEGER: TA_InstrumentInitWithUserKey( &instrument, 0 ); break; case TA_KEY_TYPE_CAT: TA_InstrumentInit( &instrument, "CATONLY", "" ); break; case TA_KEY_TYPE_SYM: TA_InstrumentInit( &instrument, "", "SYMONLY" ); break; case TA_KEY_TYPE_CATSYM: TA_InstrumentInit( &instrument, "C", "S" ); break; default: return TA_PM_ERR_INVALID_KEY_TYPE; } transaction.id = &instrument; transaction.price = 10.00; transaction.quantity = 1234; TA_TimestampCopy( &transaction.timestamp, ×tampNowPlusOneYear ); transaction.type = transactionType; retCode = TA_TradeLogAdd( tradeLog, &transaction ); if( retCode != TA_SUCCESS ) { printf( "Failed: TA_TradeLogAdd bad retCode %d\n", retCode ); return TA_PM_2TRADETST_TRADELOGADD_1; } /* Set the corresponsing exit transaction type. * Also make the exit price either winning * or loosing. */ if( transactionType == TA_LONG_ENTRY ) { transaction.type = TA_LONG_EXIT; if( winningTrade ) transaction.price = 12.00; else transaction.price = 9.50; } else if( transactionType == TA_SHORT_ENTRY ) { transaction.type = TA_SHORT_EXIT; if( winningTrade ) transaction.price = 9.25; else transaction.price = 11.00; } else return TA_PM_2TRADETST_BAD_TRADE_TYPE; /* Add the exit transaction. */ retCode = TA_TradeLogAdd( tradeLog, &transaction ); if( retCode != TA_SUCCESS ) { TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_TradeLogAdd bad retCode %d\n", retCode ); return TA_PM_2TRADETST_TRADELOGADD_2; } /* Create a TA_PM */ retCode = TA_PMAlloc( ×tampNow, ×tampNowPlusOneYear, 1000, &allocatedPM ); if( retCode != TA_SUCCESS ) { TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMAlloc bad retCode %d\n", retCode ); return TA_PM_2TRADETST_PMALLOC_FAILED; } retCode = TA_PMAddTradeLog( allocatedPM, tradeLog ); if( retCode != TA_SUCCESS ) { TA_PMFree( allocatedPM ); TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMAddTradeLog bad retCode %d\n", retCode ); return TA_PM_2TRADETST_PMADDTRADELOG_FAILED; } /* Verify the NB of TRADE and the net profit */ if( transactionType == TA_LONG_ENTRY ) { if( winningTrade ) errorNumber = checkPMvalues( allocatedPM, 1, 0, 2468, 0 ); else errorNumber = checkPMvalues( allocatedPM, 1, 0, -617, 0 ); } else if( transactionType == TA_SHORT_ENTRY ) { if( winningTrade ) errorNumber = checkPMvalues( allocatedPM, 0, 1, 0, 925.5 ); else errorNumber = checkPMvalues( allocatedPM, 0, 1, 0, -1234); } else errorNumber = TA_PM_UNKNOWN_TRANSACTION_TYPE; if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = checkNoHang( allocatedPM ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Building a report should work */ errorNumber = test_report( allocatedPM, 0 ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Clean-up and exit */ retCode = TA_PMFree( allocatedPM ); if( retCode != TA_SUCCESS ) { TA_PMFree( allocatedPM ); TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMFree bad retCode %d\n", retCode ); return TA_PM_2TRADETST_PMFREE_FAILED; } retCode = TA_TradeLogFree( tradeLog ); if( retCode != TA_SUCCESS ) { printRetCode( retCode ); printf( "Failed: TA_TradeLogFree bad retCode %d\n", retCode ); return TA_PM_TRADELOGFREE_ONE_TRADE_FAILED; } return TA_TEST_PASS; }
static ErrorNumber test_onetransaction_only( TA_KEY_TYPE keyTypeTest ) { TA_RetCode retCode; TA_Instrument instrument; TA_Transaction transaction; TA_TradeLog *tradeLog; ErrorNumber errorNumber; TA_PM *allocatedPM; /* Allocate an empty TA_TradeLog. */ retCode = TA_TradeLogAlloc( &tradeLog ); if( (retCode != TA_SUCCESS) || (tradeLog == NULL) ) { printRetCode( retCode ); printf( "Failed: TA_TradeLogAlloc bad retCode! [%d]\n", retCode ); return TA_PM_EMPTY_TA_TRADE_LOG_TESTS_0; } /* Add one TA_Transaction */ switch( keyTypeTest ) { case TA_KEY_TYPE_INTEGER: TA_InstrumentInitWithUserKey( &instrument, 0x12345432 ); break; case TA_KEY_TYPE_CAT: TA_InstrumentInit( &instrument, "CATONLY", NULL ); break; case TA_KEY_TYPE_SYM: TA_InstrumentInit( &instrument, NULL, "S" ); break; case TA_KEY_TYPE_CATSYM: TA_InstrumentInit( &instrument, "CATABCDEFGHIJKLMNOPQRSTUVWXYZ", "SYM012345678901234567890" ); break; default: return TA_PM_ERR_INVALID_KEY_TYPE; } transaction.id = &instrument; transaction.price = 34.45; transaction.quantity = 8765; TA_TimestampCopy( &transaction.timestamp, ×tampNow ); transaction.type = TA_LONG_ENTRY; retCode = TA_TradeLogAdd( tradeLog, &transaction ); if( retCode != TA_SUCCESS ) { printRetCode( retCode ); printf( "Failed: TA_TradeLogAdd bad retCode %d\n", retCode ); return TA_PM_TRADELOGADD_ONE_TRADE_FAILED; } /* Create a TA_PM */ retCode = TA_PMAlloc( ×tampNow, ×tampNow, 1000, &allocatedPM ); if( retCode != TA_SUCCESS ) { TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMAlloc bad retCode %d\n", retCode ); return TA_PM_TRADELOGADD_ONE_TRADE_FAILED_1; } retCode = TA_PMAddTradeLog( allocatedPM, tradeLog ); if( retCode != TA_SUCCESS ) { TA_PMFree( allocatedPM ); TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMAddTradeLog bad retCode %d\n", retCode ); return TA_PM_TRADELOGADD_ONE_TRADE_FAILED_2; } /* Verify the NB of TRADE */ errorNumber = checkPMvalues( allocatedPM, 0, 0, 0, 0 ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = checkNoHang( allocatedPM ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Building a report should work */ errorNumber = test_report( allocatedPM, 0 ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Clean-up and exit */ retCode = TA_PMFree( allocatedPM ); if( retCode != TA_SUCCESS ) { TA_PMFree( allocatedPM ); TA_TradeLogFree( tradeLog ); printRetCode( retCode ); printf( "Failed: TA_PMFree bad retCode %d\n", retCode ); return TA_PM_TRADELOGADD_ONE_TRADE_FAILED_3; } retCode = TA_TradeLogFree( tradeLog ); if( retCode != TA_SUCCESS ) { printRetCode( retCode ); printf( "Failed: TA_TradeLogFree bad retCode %d\n", retCode ); return TA_PM_TRADELOGFREE_ONE_TRADE_FAILED; } return TA_TEST_PASS; }
/**** Global functions definitions. ****/ ErrorNumber test_pm( void ) { ErrorNumber errorNumber; TA_UDBase *udb; unsigned int i, j; printf( "Testing Performance Measurement\n" ); /* Side Note: * Why all these allocLib/freeLib in this function? * Each time freeLib is being called, it is verified * that all ressource has been freed. So that's a good * way to verify for any potential memory leak. */ /* Initialize some globals used throughout these tests. */ TA_SetTimeNow( ×tampNow ); TA_SetDateNow( ×tampNow ); TA_NextWeekday( ×tampNow ); TA_TimestampCopy( ×tampNowPlusOneYear, ×tampNow ); TA_NextYear( ×tampNowPlusOneYear ); TA_PrevDay( ×tampNowPlusOneYear ); /* Using a user defined kkey */ TA_InstrumentInitWithUserKey( &id1_1, 12 ); TA_InstrumentInitWithUserKey( &id1_2, 9 ); /* Using a category / symbol strings. */ TA_InstrumentInit( &id2_1, "AB", "CD" ); TA_InstrumentInit( &id2_1, "AB", "CE" ); /* Using a category only. */ TA_InstrumentInit( &id3_1, "ABCD", NULL ); TA_InstrumentInit( &id3_2, "EFGH", NULL ); /* Using only a symbol string */ TA_InstrumentInit( &id4_1, NULL, "A" ); TA_InstrumentInit( &id4_2, NULL, "B" ); /* Test limit cases with empty TA_TradeLog */ errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_emptytradelog(); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: Empty trade log cases\n" ); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Test with only one TA_Transaction. * Repeat all tests for each possible * TA_Instrument key type. */ for( i=0; i < NB_TA_KEY_TYPE; i++ ) { errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_onetransaction_only( (TA_KEY_TYPE)i ); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: one transaction cases (key=%d,errorNumber=%d)\n", (TA_KEY_TYPE)i, errorNumber ); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; } /* Tests with two TA_Transaction for the * same given TA_Instrument. * * Repeat the test for each combination * of: * - TA_Instrument key type * - long and short trade. * - winning and losing trade */ for( i=0; i <= 1; i++ ) { /* 0 = test a loosing trade * 1 = test a winning trade */ for( j=0; j < NB_TA_KEY_TYPE; j++ ) { /* Test Long */ errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_onetrade_only( (TA_KEY_TYPE)j, TA_LONG_ENTRY, i ); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: one trade only (key=%d,type=%d,winning=%d)\n", (TA_KEY_TYPE)j, TA_LONG_ENTRY, i ); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; /* Test Short */ errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_onetrade_only( (TA_KEY_TYPE)j, TA_SHORT_ENTRY, i ); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: one trade only (key=%d,type=%d,winning=%d)\n", (TA_KEY_TYPE)j, TA_SHORT_ENTRY, i ); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; } } /* Test TA_PMValueId using a list of tests * defined in static variables. */ for( i=0; i < NB_PMVALUEID_TEST; i++ ) { errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_valueId( &pmValueIdTests[i] ); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: test_valueId #%d\n", i); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; } /* Test TA_PMArrayId using a list of tests * defined in static variables. */ for( i=0; i < NB_PMARRAYID_TEST; i++ ) { errorNumber = allocLib( &udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; errorNumber = test_arrayId( &pmArrayIdTests[i] ); if( errorNumber != TA_TEST_PASS ) { printf( "Failed: test_arrayId #%d\n", i); return errorNumber; } errorNumber = freeLib( udb ); if( errorNumber != TA_TEST_PASS ) return errorNumber; } return TA_TEST_PASS; }
static TA_Timestamp *allocTimestampArray( const TA_Timestamp *start, const TA_Timestamp *end, int *nbDays ) { TA_RetCode retCode; TA_Timestamp *array; int outIdx; TA_Timestamp curDate; TA_DayOfWeek dayOfTheWeek; TA_ASSERT_RET( TA_TimestampValidate(start) == TA_SUCCESS, (TA_Timestamp *)NULL ); TA_ASSERT_RET( TA_TimestampValidate(end ) == TA_SUCCESS, (TA_Timestamp *)NULL ); TA_ASSERT_RET( nbDays != NULL, (TA_Timestamp *)NULL ); /* Calculate the exact number of week days * between start and end inclusive. * Excluding week-ends. */ retCode = TA_TimestampDeltaWeekday( start, end, (unsigned int *)nbDays ); if( retCode != TA_SUCCESS ) return (TA_Timestamp *)NULL; /* Allocate the array. Add two element just to be on the safe side. */ array = TA_Malloc( sizeof( TA_Timestamp ) * ((*nbDays)+2) ); if( !array ) return (TA_Timestamp *)NULL; /* Fill up the array. */ TA_TimestampCopy( &curDate, start ); /* Write the start point, if it is a weekday. */ outIdx = 0; dayOfTheWeek = TA_GetDayOfTheWeek( &curDate ); if( (dayOfTheWeek != TA_SUNDAY) && (dayOfTheWeek != TA_SATURDAY) ) { TA_TimestampCopy( &array[outIdx], &curDate ); outIdx++; TA_NextWeekday( &curDate ); TA_ASSERT_RET( TA_TimestampValidate(&curDate) == TA_SUCCESS, (TA_Timestamp *)NULL ); } /* Count the number of weekday */ while( TA_TimestampLess( &curDate, end ) ) { TA_TimestampCopy( &array[outIdx], &curDate ); outIdx++; TA_NextWeekday( &curDate ); TA_ASSERT_RET( TA_TimestampValidate(&curDate) == TA_SUCCESS, (TA_Timestamp *)NULL ); } /* Check if the ending point is a weekday. */ if( TA_TimestampEqual( &curDate, end ) ) { dayOfTheWeek = TA_GetDayOfTheWeek( &curDate ); if( (dayOfTheWeek != TA_SUNDAY) && (dayOfTheWeek != TA_SATURDAY) ) TA_TimestampCopy( &array[outIdx++], &curDate ); } TA_ASSERT_RET( outIdx == (*nbDays), (TA_Timestamp *)NULL ); return array; }