How parse non OHLC json file (AmiQuote 4.10, javascript), save as csv-file?

@Tomasz asked me to show my json file in this thread. To avoid hjacking the thread, I decided to start a new one.

This is a json file from Finnhub.io for EpsSurprise of AAPL from 2021-09-17:

[{"actual":null,"estimate":1.2507342000000001,"period":"2021-12-31","surprise":null,"surprisePercent":null,"symbol":"AAPL"},{"actual":1.3,"estimate":1.0269258,"period":"2021-09-30","surprise":0.273074,"surprisePercent":26.5914,"symbol":"AAPL"},{"actual":1.4,"estimate":1.0064237999999999,"period":"2021-06-30","surprise":0.393576,"surprisePercent":39.1064,"symbol":"AAPL"},{"actual":1.68,"estimate":1.4397606,"period":"2021-03-31","surprise":0.240239,"surprisePercent":16.6861,"symbol":"AAPL"}]

In my notepad++ json viewer it looks like this:
image

Currently I do convert this file via a bat-file to csv (a very slow process via a converter which works with node.js) and import the resulting csv-file in Amibroker as Static variable to use in explorations. I also save these converted csv files for later use.

My goal is to keep my current solution with the import as static variables and saving the converted csv-file. The new approach should only improve the speed of the conversion. I do not want to populate my database with fundamental data for now, as I prefer it to be as lean as possible and easily replaceable. To be honest, I fear I would make it too complicate and would lose overview and would not be able to easily check, if things are still correct.

Since I do not like to ask for help without trying at least a bit (I have zero knowledge of Javascript): I looked again at the Polygon.IO example and think, this here is the main part for the parsing.

		output = "";
		for( i = 0; i < quotes.length; i++ )
		{
			date = new Date( quotes[ i ].t );

			open = quotes[ i ].o;
			high = quotes[ i ].h;
			low = quotes[ i ].l;
			close = quotes[ i ].c;
			volume = quotes[ i ].v;
		
			output += dateToYMD( date ) + ", ";
			output += dateToHMS( date ) + ", ";

			output += open + ", ";
			output += high + ", ";
			output += low + ", ";
			output += close + ", ";		
			output += volume + "\n";		
		}

I tried a bit with different coding and settings: I got one crash of AQ and often the Error "TypeError: 'quotes.lenght' is Null or no object." (translated into English by me). I think I got this thing totally wrong and I do not really understand how the line "quotes = all.results;" works, as the error seems to point to it.

2021-09-19 20_00_28-AmiQuote - Unbenannt

This is my Finnhub.io.ads file of the try above:

<AmiQuote Version="4.00">
<DataSource>
<Name>Finnhub.io</Name>
<Description>Test to convert json to csv and NOT importing it</Description>
<URLTemplate>C:\Program Files\AmiBroker\MyFiles\Data\test\{symbol}.json</URLTemplate>
<ErrorMsgPrefix>Error:</ErrorMsgPrefix>
<Options>
<UseTodayAsEndDate>0</UseTodayAsEndDate>
<RunJavascript>1</RunJavascript>
</Options>
<Limits ReqPerMinute="5"/>
<CSVFormatOptions RemoveDoubleQuotes="0" ConvertISODateTime="0"/>
<ImporterOptions>
<FormatEOD>Skip,Skip,Skip,Skip,Skip,Skip,Skip</FormatEOD>
<FormatIntra>Skip,Skip,Skip,Skip,Skip,Skip,Skip</FormatIntra>
<FileExtension>csv</FileExtension>
<Separators>,</Separators>
</ImporterOptions>
<SupportedIntervals>
<Interval Period="1440" MaxDays="3650" ExtraToken="1/day"/>
<Interval Period="1" MaxDays="0" ExtraToken=""/>
<Interval Period="5" MaxDays="0" ExtraToken=""/>
<Interval Period="10" MaxDays="0" ExtraToken=""/>
<Interval Period="15" MaxDays="0" ExtraToken=""/>
<Interval Period="30" MaxDays="0" ExtraToken=""/>
<Interval Period="60" MaxDays="0" ExtraToken=""/>
</SupportedIntervals>
<Javascript>function pad(num, size) 
{
    num = num.toString();
    while (num.length &lt; size) num = "0" + num;
    return num;
}

function dateToYMD( d )
{
	return  d.getFullYear() + "-" +
           pad( d.getMonth() + 1, 2 ) + "-" +
           pad( d.getDate(), 2 );
}

function dateToHMS( d )
{
	return	 pad( d.getHours(), 2 ) + ":" +
           pad( d.getMinutes() + 1, 2 ) + ":" +
           pad( d.getSeconds(), 2 );
}

// the processing function takes text as input and produces text as ouput
function Process( input )
{
	try
	{
		all = JSON.parse( input );

		if( all.status == "ERROR" ) 
		{
			return "Error: " + all.error;
		}

		if( all.resultsCount == 0 )
		{
			return "Error: no data for this symbol";
		}

		quotes = all.results;

		output = "";
		for( i = 0; i &lt; quotes.length; i++ )
		{
			date = new Date( quotes[ i ].t );

			actual = quotes[ i ].o;
			estimate = quotes[ i ].h;
			period = quotes[ i ].l;
		
			output += dateToYMD( date ) + ", ";
			output += dateToHMS( date ) + ", ";

			output += actual + ", ";
			output += estimate + ", ";
			output += period + ", ";
		
		}
	}
	catch( e )
   {
      output = "Error: " + e.name + ": " + e.message;
   }

	return output;
}</Javascript>
</DataSource>
</AmiQuote>

I now ask myself:

  1. Can AmiQuote even just convert my json files and save them as csv?
  2. Can AmiQuote do this without actually importing the file?

My thoughts for question 1:
As I understand the new "User-definable data source"-tab "Debug": AmiQuote reads the source-file and displays the content in the "Response"-field. AmiQuote then tries to run the Javascript code (if checkbox is set) and writes the output in the "Processed content"-field. So in theory my question 1 should be possible by writing the correct javascript code.

And for question 2:
I think I should also be able to do that by using "skip" for all ASCII import settings. Though, I am unclear about the file extension: Will "csv" work?

Can anybody give me some hints or examples or is even so kind to solve it?

3 Likes

The answers to your questions are:

  1. Yes (your JSON is different than Polygon sample but you can still process the file)
  2. Yes (just uncheck "Auto-import" checkbox in AmiQuote

Here is a Javascript code that makes conversion of your JSON to CSV:

// the processing function takes text as input and produces text as ouput
function Process( input )
{
   items = JSON.parse(input);

   csv = "";

   for( i = 0; i < items.length; i++ )
   {
       item = items[ i ];
       csv += item.symbol + "," + item.estimate + "," + item.period + "\n";
   }

   return csv;
}
10 Likes

Another example, to parse "Basic financials" information from Finnhub API like this:

{
  "metric": {
    "10DayAverageTradingVolume": 99.500715,
    "13WeekPriceReturnDaily": 70.14609,
    "26WeekPriceReturnDaily": 135.81072,
    "3MonthAverageTradingVolume": 2099.330595,
    "52WeekHigh": 1376.28,
    "52WeekHighDate": "affirming",
    "52WeekLow": 883.125,
    "52WeekLowDate": "prompt",
    "52WeekPriceReturnDaily": 215.03128500000003,
    "5DayPriceReturnDaily": 6.330509999999999,
    "assetTurnoverAnnual": 2.3820300000000003,
    "assetTurnoverTTM": 2.3820300000000003,
    "beta": 3.511935,
    "bookValuePerShareAnnual": 84.97750500000001,
    "bookValuePerShareQuarterly": 84.97750500000001,
    "bookValueShareGrowth5Y": 69.36885,
    "capitalSpendingGrowth5Y": 89.279865,
    "cashFlowPerShareAnnual": 43.152795000000005,
    "cashFlowPerShareTTM": 43.154235,
    "cashPerSharePerShareAnnual": 78.00277500000001,
    "cashPerSharePerShareQuarterly": 78.00277500000001,
    "currentDividendYieldTTM": 3.28644,
    "currentEv/freeCashFlowAnnual": 275.654655,
    "currentEv/freeCashFlowTTM": 275.654655,
    "currentRatioAnnual": 9.359955,
    "currentRatioQuarterly": 9.359955,
    "dividendGrowthRate5Y": 44.537445,
    "dividendPerShare5Y": 8.244,
    "dividendPerShareAnnual": 9.855,
    "dividendYield5Y": 5.312475,
    "dividendYieldIndicatedAnnual": 3.721635,
    "dividendsPerShareTTM": 9.855,
    "ebitdPerShareTTM": 48.267765,
    "ebitdaCagr5Y": 86.714865,
    "ebitdaInterimCagr5Y": 86.714865,
    "epsBasicExclExtraItemsAnnual": 36.533654999999996,
    "epsBasicExclExtraItemsTTM": 36.54022500000001,
    "epsExclExtraItemsAnnual": 36.24075,
    "epsExclExtraItemsTTM": 36.247815,
    "epsGrowth3Y": 123.832305,
    "epsGrowth5Y": 115.78653000000001,
    "epsGrowthQuarterlyYoy": 217.418715,
    "epsGrowthTTMYoy": 178.84053,
    "epsInclExtraItemsAnnual": 36.24075,
    "epsInclExtraItemsTTM": 36.247815,
    "epsNormalizedAnnual": 36.247365,
    "focfCagr5Y": 104.202405,
    "freeCashFlowAnnual": 178186.5,
    "freeCashFlowPerShareTTM": 23.421689999999998,
    "freeCashFlowTTM": 178186.5,
    "freeOperatingCashFlow/revenue5Y": 93.11256,
    "freeOperatingCashFlow/revenueTTM": 106.00785,
    "grossMargin5Y": 300.55329,
    "grossMarginAnnual": 310.1661,
    "grossMarginTTM": 310.1661,
    "inventoryTurnoverAnnual": 103.74929999999999,
    "inventoryTurnoverTTM": 103.74929999999999,
    "longTermDebt/equityAnnual": 195.93769500000002,
    "longTermDebt/equityQuarterly": 195.93769500000002,
    "marketCapitalization": 10140709.5,
    "monthToDatePriceReturnDaily": -2.996235,
    "netDebtAnnual": -268411.5,
    "netDebtInterim": -268411.5,
    "netIncomeEmployeeAnnual": 1603020.5999999999,
    "netIncomeEmployeeTTM": 1523312.0999999999,
    "netInterestCoverageAnnual": null,
    "netInterestCoverageTTM": null,
    "netMarginGrowth5Y": 45.445005,
    "netProfitMargin5Y": 140.273775,
    "netProfitMarginAnnual": 164.03283,
    "netProfitMarginTTM": 164.03283,
    "operatingMargin5Y": 160.49547,
    "operatingMarginAnnual": 187.14212999999998,
    "operatingMarginTTM": 187.14212999999998,
    "payoutRatioAnnual": 123.907725,
    "payoutRatioTTM": 123.907725,
    "pbAnnual": 71.458515,
    "pbQuarterly": 71.458515,
    "pcfShareTTM": 138.995685,
    "peBasicExclExtraTTM": 150.12837,
    "peExclExtraAnnual": 167.55633,
    "peExclExtraHighTTM": 158.875515,
    "peExclExtraTTM": 167.523705,
    "peExclLowTTM": 95.28030000000001,
    "peInclExtraTTM": 167.523705,
    "peNormalizedAnnual": 167.525775,
    "pfcfShareAnnual": 256.09788000000003,
    "pfcfShareTTM": 256.09788000000003,
    "pretaxMargin5Y": 163.68102,
    "pretaxMarginAnnual": 190.35207,
    "pretaxMarginTTM": 190.35207,
    "priceRelativeToS\u0026P50013Week": 38.87154,
    "priceRelativeToS\u0026P50026Week": 67.10832,
    "priceRelativeToS\u0026P5004Week": -5.770395000000001,
    "priceRelativeToS\u0026P50052Week": 53.614215,
    "priceRelativeToS\u0026P500Ytd": 64.053855,
    "psAnnual": 60.329745,
    "psTTM": 60.329745,
    "ptbvAnnual": 120.041055,
    "ptbvQuarterly": 120.041055,
    "quickRatioAnnual": 9.226170000000002,
    "quickRatioQuarterly": 9.226170000000002,
    "receivablesTurnoverAnnual": 21.594645,
    "receivablesTurnoverTTM": 21.594645,
    "revenueEmployeeAnnual": 4397651.100000001,
    "revenueEmployeeTTM": 4178983.5,
    "revenueGrowth3Y": 67.75083000000001,
    "revenueGrowth5Y": 58.586355,
    "revenueGrowthQuarterlyYoy": 96.062625,
    "revenueGrowthTTMYoy": 78.892785,
    "revenuePerShareAnnual": 99.421155,
    "revenuePerShareTTM": 99.424395,
    "revenueShareGrowth5Y": 63.88938,
    "roaRfy": 86.82849,
    "roaa5Y": 66.37931999999999,
    "roae5Y": 181.34334,
    "roaeTTM": 211.854015,
    "roeRfy": 211.854015,
    "roeTTM": 86.82849,
    "roi5Y": 87.63048,
    "roiAnnual": 116.30717999999999,
    "roiTTM": 116.30717999999999,
    "tangibleBookValuePerShareAnnual": 50.55813,
    "tangibleBookValuePerShareQuarterly": 50.55813,
    "tbvCagr5Y": 48.98547,
    "totalDebt/totalEquityAnnual": 224.02701,
    "totalDebt/totalEquityQuarterly": 224.02701,
    "totalDebtCagr5Y": 24.466185000000003,
    "yearToDatePriceReturnDaily": 156.696795
  },
  "symbol": "MSFT"
}

You can use Javascript like this:

// the processing function takes text as input and produces text as ouput
function Process( input )
{
   items = JSON.parse(input);

	csv = "#";

	// get all property names in metric object
   for( item in items.metric )
   {
		csv += item + ",";
   }

   csv += "\n";

   // get values of all properties in the metric object
   for( item in items.metric )
   {
		csv += items.metric[ item ] + ",";
   }


	return csv;
}

A sample Finnhub.IO data source config file for AmiQuote 4.10 is here (you would need an API key from Finnhub to test it - a free "sandbox" key will work fine):FinnHub.IO.ads (1.5 KB)

Note it is for demonstration purposes, check out "Debug" page to see how Javascript transforms JSON into CSV file with automatically generated header line. The config file does NOT define import fields for AmiBroker, so it is download-only demo, no import.

AmiQuote using this file will produce CSV like this:

#10DayAverageTradingVolume,13WeekPriceReturnDaily,26WeekPriceReturnDaily,3MonthAverageTradingVolume,52WeekHigh,52WeekHighDate,52WeekLow,52WeekLowDate,52WeekPriceReturnDaily,5DayPriceReturnDaily,assetTurnoverAnnual,assetTurnoverTTM,beta,bookValuePerShareAnnual,bookValuePerShareQuarterly,bookValueShareGrowth5Y,capitalSpendingGrowth5Y,cashFlowPerShareAnnual,cashFlowPerShareTTM,cashPerSharePerShareAnnual,cashPerSharePerShareQuarterly,currentDividendYieldTTM,currentEv/freeCashFlowAnnual,currentEv/freeCashFlowTTM,currentRatioAnnual,currentRatioQuarterly,dividendGrowthRate5Y,dividendPerShare5Y,dividendPerShareAnnual,dividendYield5Y,dividendYieldIndicatedAnnual,dividendsPerShareTTM,ebitdPerShareTTM,ebitdaCagr5Y,ebitdaInterimCagr5Y,epsBasicExclExtraItemsAnnual,epsBasicExclExtraItemsTTM,epsExclExtraItemsAnnual,epsExclExtraItemsTTM,epsGrowth3Y,epsGrowth5Y,epsGrowthQuarterlyYoy,epsGrowthTTMYoy,epsInclExtraItemsAnnual,epsInclExtraItemsTTM,epsNormalizedAnnual,focfCagr5Y,freeCashFlowAnnual,freeCashFlowPerShareTTM,freeCashFlowTTM,freeOperatingCashFlow/revenue5Y,freeOperatingCashFlow/revenueTTM,grossMargin5Y,grossMarginAnnual,grossMarginTTM,inventoryTurnoverAnnual,inventoryTurnoverTTM,longTermDebt/equityAnnual,longTermDebt/equityQuarterly,marketCapitalization,monthToDatePriceReturnDaily,netDebtAnnual,netDebtInterim,netIncomeEmployeeAnnual,netIncomeEmployeeTTM,netInterestCoverageAnnual,netInterestCoverageTTM,netMarginGrowth5Y,netProfitMargin5Y,netProfitMarginAnnual,netProfitMarginTTM,operatingMargin5Y,operatingMarginAnnual,operatingMarginTTM,payoutRatioAnnual,payoutRatioTTM,pbAnnual,pbQuarterly,pcfShareTTM,peBasicExclExtraTTM,peExclExtraAnnual,peExclExtraHighTTM,peExclExtraTTM,peExclLowTTM,peInclExtraTTM,peNormalizedAnnual,pfcfShareAnnual,pfcfShareTTM,pretaxMargin5Y,pretaxMarginAnnual,pretaxMarginTTM,priceRelativeToS&P50013Week,priceRelativeToS&P50026Week,priceRelativeToS&P5004Week,priceRelativeToS&P50052Week,priceRelativeToS&P500Ytd,psAnnual,psTTM,ptbvAnnual,ptbvQuarterly,quickRatioAnnual,quickRatioQuarterly,receivablesTurnoverAnnual,receivablesTurnoverTTM,revenueEmployeeAnnual,revenueEmployeeTTM,revenueGrowth3Y,revenueGrowth5Y,revenueGrowthQuarterlyYoy,revenueGrowthTTMYoy,revenuePerShareAnnual,revenuePerShareTTM,revenueShareGrowth5Y,roaRfy,roaa5Y,roae5Y,roaeTTM,roeRfy,roeTTM,roi5Y,roiAnnual,roiTTM,tangibleBookValuePerShareAnnual,tangibleBookValuePerShareQuarterly,tbvCagr5Y,totalDebt/totalEquityAnnual,totalDebt/totalEquityQuarterly,totalDebtCagr5Y,yearToDatePriceReturnDaily,
99.500715,70.14609,135.81072,2099.330595,1376.28,affirming,883.125,prompt,215.03128500000002,6.330509999999999,2.3820300000000003,2.3820300000000003,3.511935,84.97750500000001,84.97750500000001,69.36885,89.279865,43.152795000000005,43.154235,78.00277500000001,78.00277500000001,3.28644,275.654655,275.654655,9.359955,9.359955,44.537445,8.244,9.855,5.312475,3.721635,9.855,48.267765,86.714865,86.714865,36.533654999999996,36.54022500000001,36.24075,36.247815,123.832305,115.78653000000001,217.418715,178.84053,36.24075,36.247815,36.247365,104.202405,178186.5,23.421689999999998,178186.5,93.11256,106.00785,300.55329,310.1661,310.1661,103.74929999999999,103.74929999999999,195.93769500000002,195.93769500000002,10140709.5,-2.996235,-268411.5,-268411.5,1603020.5999999998,1523312.0999999998,null,null,45.445005,140.273775,164.03283,164.03283,160.49547,187.14212999999998,187.14212999999998,123.907725,123.907725,71.458515,71.458515,138.995685,150.12837,167.55633,158.875515,167.523705,95.28030000000001,167.523705,167.525775,256.09788000000003,256.09788000000003,163.68102,190.35207,190.35207,38.87154,67.10832,-5.770395000000001,53.614215,64.053855,60.329745,60.329745,120.041055,120.041055,9.226170000000001,9.226170000000001,21.594645,21.594645,4397651.100000001,4178983.5,67.75083000000001,58.586355,96.062625,78.892785,99.421155,99.424395,63.88938,86.82849,66.37931999999999,181.34334,211.854015,211.854015,86.82849,87.63048,116.30717999999999,116.30717999999999,50.55813,50.55813,48.98547,224.02701,224.02701,24.466185000000003,156.696795,
8 Likes

Thank you Tomasz for this very speedy solution and your second example as well. I noticed, the converted csv-file has no header, so I hardcoded it like this:

// the processing function takes text as input and produces text as ouput
function Process( input )
{
   output = JSON.parse(input);

   csv = "";

	// hardcoded header
	csv += "symbol,estimate,period,surprise,surprisePercent";

	csv += "\n";

	// values
   for( i = 0; i < output.length; i++ )
   {
       item = output[ i ];
       csv += item.symbol + "," + item.estimate + "," + item.period + "," + item.surprise + "," + item.surprisePercent + "\n";
   }

   return csv;
}

The csv-file looks like this:

$NAME AAPL
symbol,estimate,period,surprise,surprisePercent
AAPL,1.2507342000000001,2021-12-31,null,null
AAPL,1.0269258,2021-09-30,0.273074,26.5914
AAPL,1.0064237999999998,2021-06-30,0.393576,39.1064
AAPL,1.4397606,2021-03-31,0.240239,16.6861

I tried to use the code of your second example to access the headers dynamicly, but I was only able to get to "0,1,2,3". The missing counterpart of "metric" as in your second examples causes some headaches.

// the processing function takes text as input and produces text as ouput
function Process( input )
{
   output = JSON.parse(input);

   csv = "";

	// get all property names for the csv file header
   for( item in output ) //no matter what I added after "output", I was not able to extract the header. I tried just a point, ".0", ".null" and just "".
   {
		csv += item + ",";
   }

   csv += "\n";

	// values
   for( i = 0; i < output.length; i++ )
   {
       item = output[ i ];
       csv += item.symbol + "," + item.estimate + "," + item.period + "," + item.surprise + "," + item.surprisePercent + "\n";
   }

   return csv;
}

This is the csv-file result:

$NAME AAPL
0,1,2,3,
AAPL,1.2507342000000001,2021-12-31,null,null
AAPL,1.0269258,2021-09-30,0.273074,26.5914
AAPL,1.0064237999999998,2021-06-30,0.393576,39.1064
AAPL,1.4397606,2021-03-31,0.240239,16.6861

Note for other users:
The csv-file is found (after running AmiQuote, not the debug!) in the AmiQuote Download folder, e.g.: ..\Program Files\AmiBroker\AmiQuote\Download

1 Like

It DOES HAVE the header. See second example

Each API returns different output. In my second example the output generated by Finnhub API had "metric" object. That is why Javascript referred to it. If given API returns something else, you have to adjust Javascript to refer to object that is PRESENT in JSON.

// the "metric" is the OBJECT name in the JSON file returned by particular vendor API endpoint
{
  "metric": { /* HERE IT IS */
    "10DayAverageTradingVolume": 99.500715,

There is no single formula for everything because APIs from different vendors are different. Even single vendor has multiple APIs (endpoints) and each endpoint returns something else. You need a Javascript that is appropriate for given endpoint API.

You need a lot of reading about JSON see JSON - Wikipedia
In essence JSON is a notation that represents Javascript OBJECT content as text.

Javascript JSON.parse can parse the TEXT and convert it back to actual Javascript OBJECT. Then OBJECT can be accessed from Javascript code by its name.

3 Likes

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.