On 23 Dec 2008, I wrote:
> Recently, there was discussion of OHLC and Candlestick charting of
> stock market data. Rather than reinvent the wheel, I thought I'd ask
> first: has anyone created J code that will convert stock market data
> into a point & figure chart and that they'd be willing to share?
There being no responses with such code, I went ahead and wrote an
explicit verb that creates an array containing P&F chart data (see
bottom of this post). Admittedly, it's not very J-ish at all, but it
does seem to work. Anyway, before I can have a finalized version (I
probably should prepend an initial column containing the price values
for each box), I need the answers to some questions:
(1) How do I pass *six* arguments to the verb? (nBox, nRevBoxes,
nMaxColumns, nMaxRows, nShowDate, datafilename--the latter would be
passed to the readcsv line below) (depending on whether nChartLow and
nChartHigh are derived from the data or from user input, these might
possibly be additional arguments, for a total of *eight* arguments)
How would any of these multiple arguments be made "optional" (i.e.,
they could be present or not)?
(2) How can the resulting array be "plotted" (or converted into some
sort of resizable diagram/chart/whatever) in a way that shows all the
(perhaps tiny) X's, O's, and date digits? It would be nice, too, to
impose a gridwork surrounding the array elements.
Harvey
NB. #################################################################
NB. pf.ijs - standard "Point & Figure" charting algorithm with optional
dating added
NB. by Harvey E. Hahn -- 23-25,29-31 Jan; 1,4,6-7 Feb 2009
NB. Initial datatype prefixes used to keep names straight:
NB. "b" for boxed
NB. "s" for string (literal)
NB. "n" for numeric
require 'files'
pfchart=: 3 : 0
NB. constants (should be input by user instead?):
nBox=. 150 NB. size of charting box (related to nMaxRows below))
nRevBoxes=. 3 NB. number of boxes needed for reversal
nMaxColumns=. 125
nMaxRows=. 50 NB. this can affect value of "nBox" if calculated by
formula
nShowDate=. 1 NB. flag to display dates or not
NB. initialize variables:
nBoxHigh=. 0
nBoxLow=. 0
nHigh=. 0
nLow=. 0
nClose=. 0
nNewHigh=. 0
nNewLow=. 0
nCurrCol=. 0
nFirst=. 1 NB. flag is turned "off" (false) after first entry
nXCol=. 1
nOCol=. 0
nColType=. nXCol
sLastMonthPlotted=. ''
sLastYearPlotted=. ''
sYr=. ''
sMn=. ''
nMon=. 0
sMonth=. ''
nRowOffset=. 0
if. (1 = nShowDate) do.
nMaxRows=. nMaxRows + 4 NB. 4 extra rows needed for 4 vertical
digits of year
nRowOffset=. 4
end.
sChart=. (nMaxRows,nMaxColumns) $ ' ' NB. chart array
NB. read in (Yahoo) market data file and cull out date/high/low/close
values:
bMktData=. readcsv (jpath '~user\data\DJI-r-pf.csv')
bDate=. 0 {"1 bMktData NB. column 0 is date (format: yyyy-mm-dd)
bHigh=. 2 {"1 bMktData NB. column 2 is high
bLow=. 3 {"1 bMktData NB. column 3 is low
bClose=. 4 {"1 bMktData NB. column 4 is close
NB. two ways chart range might be done --
NB. (1) look for max and min of prices (nBox = (max-min)/boxsize),
NB. or
NB. (2) take first price and calculate range of 50% higher
NB. and 50% lower
NB. for testing purposes (DJIA 2007-2008):
nChartLow=. 7000
nChartHigh=. 14500 NB. midpoint = 10750, nBox = 150
for_i. i.((#bMktData)-1) do.
sYr=. 4 {. > i { bDate
sMn=. 2 {. 5 }. > i { bDate
nMon=. ". sMn
if. (9 < nMon) do.
sMonth=. (nMon-10) { 'abc'
else.
sMonth=. ": 1 } sMn
end.
nHigh=. (". > i { bHigh) - nChartLow
nLow=. (". > i { bLow) - nChartLow
nClose=. (". > i { bClose) - nChartLow
if. (nChartHigh < (". > i { bHigh)) +. (nChartLow > (". > i { bLow))
do.
sChart=. |. sChart
smoutput 'Data for ',(> i { bDate),' exceeded chart range'
return.
end.
NB. (initialize starting point of chart in the first column)
NB. is this the first entry?
if. (1 = nFirst) do.
nFirst=. 0 NB. turn off flag to guarantee this is done only
once:
if. nClose >= ((nHigh - nLow) % 2) do.
nColType=. nXCol
nBoxHigh=. <. (nLow % nBox)
nNewHigh=. <. (nHigh % nBox)
for_b. i.((nNewHigh-nBoxHigh)+1) do.
sChart=. 'X' (< (nBoxHigh+b+nRowOffset),nCurrCol) } sChart
end.
else.
nColType=. nOCol
nBoxLow=. <. (nHigh % nBox)
nNewLow=. <. (nLow % nBox)
for_b. i.((nBoxLow-nNewLow)+1) do.
sChart=. 'O' (< ((nBoxLow-b)+nRowOffset),nCurrCol) } sChart
end.
end.
if. 1 = nShowDate do.
if. (0 = (sLastYearPlotted -: sYr)) do.
for_c. i._4 do.
sChart=. (c{sYr) (< (3-c),nCurrCol) } sChart
end.
sLastYearPlotted=. sYr
end.
end.
else. NB. the following is the normal (i.e., non-first) procedure:
NB. convert prices to boxes:
nNewHigh=. <. (nHigh % nBox)
nNewLow=. <. (nLow % nBox)
NB. continue upward direction?
if. (nXCol = nColType) do.
if. (1 <: (nNewHigh-nBoxHigh)) do.
for_b. (1+i.(nNewHigh-nBoxHigh)) do.
sChart=. 'X' (< (nBoxHigh+b+nRowOffset),nCurrCol) } sChart
end.
if. 1 = nShowDate do.
if. (0 = (sLastMonthPlotted -: sMonth)) do.
sChart=. sMonth (< (nBoxHigh+1+nRowOffset),nCurrCol) }
sChart
sLastMonthPlotted=. sMonth
if. 1 = ".sMonth do.
if. (0 = (sLastYearPlotted -: sYr)) do.
for_c. i._4 do.
sChart=. (c{sYr) (< (3-c),nCurrCol) } sChart
end.
sLastYearPlotted=. sYr
end.
end.
end.
end.
nBoxHigh=. nNewHigh
nBoxLow=. nBoxHigh - 1 NB. for drawing purposes, 1 box below
highest 'X'
NB. no new high, so test for downside reversal:
elseif. (nRevBoxes <: (nBoxHigh - nNewLow) ) do.
if. (nCurrCol < nMaxColumns-1) do.
nCurrCol=. nCurrCol + 1
else.
sChart=. |. sChart
return.
end.
nColType=. nOCol
for_b. i.((nBoxLow-nNewLow)+1) do.
sChart=. 'O' (< ((nBoxLow-b)+nRowOffset),nCurrCol) } sChart
end.
if. 1 = nShowDate do.
if. (0 = (sLastMonthPlotted -: sMonth)) do.
sChart=. sMonth (< ((nBoxLow-2)+nRowOffset),nCurrCol) }
sChart
sLastMonthPlotted=. sMonth
if. 1 = ".sMonth do.
if. (0 = (sLastYearPlotted -: sYr)) do.
for_c. i._4 do.
sChart=. (c{sYr) (< (3-c),nCurrCol) } sChart
end.
sLastYearPlotted=. sYr
end.
end.
end.
end.
nBoxLow=. nNewLow
nBoxHigh=. nBoxLow + 1 NB. for drawing purposes, 1 box above
lowest 'O'
end.
NB. continue downward direction?
elseif. (nOCol = nColType) do.
if. (1 <: (nBoxLow-nNewLow)) do.
for_b. (1+i.(nBoxLow-nNewLow)) do.
sChart=. 'O' (< ((nBoxLow-b)+nRowOffset),nCurrCol) } sChart
end.
if. 1 = nShowDate do.
if. (0 = (sLastMonthPlotted -: sMonth)) do.
sChart=. sMonth (< ((nBoxLow-1)+nRowOffset),nCurrCol) }
sChart
sLastMonthPlotted=. sMonth
if. 1 = ".sMonth do.
if. (0 = (sLastYearPlotted -: sYr)) do.
for_c. i._4 do.
sChart=. (c{sYr) (< (3-c),nCurrCol) } sChart
end.
sLastYearPlotted=. sYr
end.
end.
end.
end.
nBoxLow=. nNewLow
nBoxHigh=. nBoxLow + 1 NB. for drawing purposes, 1 box above
lowest 'O'
NB. no new low, so test for upside reversal:
elseif. (nRevBoxes <: (nNewHigh - nBoxLow) ) do.
if. (nCurrCol < nMaxColumns-1) do.
nCurrCol=. nCurrCol + 1
else.
sChart=. |. sChart
return.
end.
nColType=. nXCol
for_b. i.((nNewHigh-nBoxHigh)+1) do.
sChart=. 'X' (< (nBoxHigh+b+nRowOffset),nCurrCol) } sChart
end.
if. 1 = nShowDate do.
if. (0 = (sLastMonthPlotted -: sMonth)) do.
sChart=. sMonth (< (nBoxHigh+2+nRowOffset),nCurrCol) }
sChart
sLastMonthPlotted=. sMonth
if. 1 = ".sMonth do.
if. (0 = (sLastYearPlotted -: sYr)) do.
for_c. i._4 do.
sChart=. (c{sYr) (< (3-c),nCurrCol) } sChart
end.
sLastYearPlotted=. sYr
end.
end.
end.
end.
nBoxHigh=. nNewHigh
nBoxLow=. nBoxHigh - 1 NB. for drawing purposes, 1 box below
highest 'X'
end.
end.
end.
end. NB. "for" loop
NB. flip chart array so that smallest coords are at lower left
NB. rather than at upper left:
sChart=. |. sChart
)
NB. #################################################################
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm