@masiegert here is a full AFL translation.
I left the _TRACEF() lines so you can further debug it if needed.
The calculation of the 1000 dates is done once and stored in a Matrix (you can force to redo it using the reset "Trigger").
The conversion from "datenum" to "datetime" sometimes result in invalid dates (I added some extra validation code, but as commented in the code, probably this is not the best way do handle it).
// https://forum.amibroker.com/t/coding-fullmoon/39820
// Port from EL code published in the 2025 Bonus edition
// of the Technical Analysis of STOCKS & COMMODITIES
// See the article titled: "New Moon--Full Moon" by P.J.Kaufman
SetBarsRequired( -2 );
reset = ParamTrigger( "Recalculate", "Go" );
dt = DateTime();
dn = DateNum();
bi = BarIndex();
if( reset )
{
StaticVarSet( "mp_Calculated", 0 );
}
function isLeapYear( y )
{
return ( ( y % 4 == 0 ) AND( y % 100 != 0 ) ) OR( y % 400 == 0 );
}
function getLastDayOfMonth( m, y )
{
switch( m )
{
case 2: // Feb
if( isLeapYear( y ) )
days = 29;
else
days = 28;
break;
case 4: // Apr
case 6: // Jun
case 9: // Sep
case 11: // Nov
days = 30;
break;
default: // Jan, Mar, May, Jul, Aug, Oct, Dec
days = 31;
break;
}
return days;
}
function TSMJulianEDtoDate( Jdate )
{
// TSMJulianEDtoDate : Convert Julian Ephemeris Date to Calendar Date
// Copyright 1999, 2025 P.J.Kaufman. All rights reserved.
local zz, fract, aa, alpha, bb, cc, dd, ee, daydec, mo, tyear, btemp, iday;
zz = int( Jdate + .5 );
fract = Jdate + .5 - zz;
if( zz < 2299161 )
aa = zz;
else
{
alpha = int( ( zz - 1867216.25 ) / 36524.25 );
aa = zz + 1 + alpha - int( alpha / 4 );
}
bb = aa + 1524;
btemp = ( bb - 122.1 ) / 365.25;
cc = int( btemp );
dd = int( 365.25 * cc );
ee = int( ( bb - dd ) / 30.6001 );
daydec = bb - dd - int( 30.6001 * ee ) + fract;
if( ee < 13.5 )
mo = ee - 1;
else
mo = ee - 13;
if( mo > 2.5 )
tyear = cc - 4716;
else
tyear = cc - 4715;
// Must round day due to TS decimal accuracy }
iday = round( daydec );
if( iDay <= 0 )
iDay = 1;
// TODO: validation for all months including Feb 29....
// MAYBE THE WRONG APPROACH - probably it is better to add a day and set to the follwing month....
ldm = getLastDayOfMonth( mo, tYear );
if( iDay > ldm )
iDay = ldm;
if( tYear >= 2000 )
abYear = 100 + tYear - 2000;
else
abYear = tYear - 1900;
retValue = abyear * 10000 + mo * 100 + iday;
// _TRACEF( "Jdate: %10.7f - Year: %g - Month: %g - Day: %g - Ret: %10.0f", jDate, tYear, Mo, iDay, retValue );
return ( floor( retValue ) );
}
function dn2s( i, date_num )
{
local str;
str = DateTimeToStr( DateTimeConvert( 2, date_num ) );
if( str == "Invalid DateTime" )
{
str = StrFormat( "%f - Invalid Date: %10.7f", i, date_num );
}
return str;
}
procedure SetArray( ix, array, dt_ )
{
local index;
index = Lookup( bi, dt_, 1 ); // find the next trading day....
if( isFinite( index ) AND index > 0 )
{
array[index] = dt_;
// _TRACEF( "%g - Setting bar %g - (%s)", ix, index, DateTimeToStr( dt_ ) );
}
}
nfm = 1000;
n = -nfm / 2;
// do it once...
mpCalculated = Nz( StaticVarGet( "mp_Calculated", 0 ) );
if( not mpCalculated )
{
moonDatesMatrix = Matrix( 2, 1000, 0 );
for( ix = 1; ix <= nfm; ix++ )
{
// new Moons relative to the year 2000
k = n;
TT = k / 1236.85; // time in Julian centuries
fullMoon = 2451550.09765 + 29.530588853 * k + 0.0001337 * TT ^ 2 - 0.000000150 * TT ^ 3 + 0.00000000073 * TT ^ 4;
// full Moons relative to the year 2000 }
k = n + .5;
TT = k / 1236.85;
newMoon = 2451550.09765 + 29.530588853 * k + 0.0001337 * TT ^ 2 - 0.000000150 * TT ^ 3 + 0.00000000073 * TT ^ 4;
fullMoonDateNum = TSMJulianEDtoDate( fullMoon );
newMoonDateNum = TSMJulianEDtoDate( newMoon );
moonDatesMatrix[0][ix - 1] = DateTimeConvert( 2, fullMoonDateNum );
moonDatesMatrix[1][ix - 1] = DateTimeConvert( 2, newMoonDateNum );
// _TRACEF( "Full moon date %g - %10.7f - JDate: %s", ix, fullMoon, dn2s(ix, fullMoonDateNum ) );
//_TRACEF( "New moon date %g - %10.7f - JDate: %s", ix, newMoon, dn2s( ix, newMoonDateNum ) );
n = n + 1;
}
StaticVarSet( "mp_Calculated", 1 );
StaticVarSet( "mp_moonDatesMatrix", moonDatesMatrix );
}
moonDatesMatrix = StaticVarGet( "mp_moonDatesMatrix" );
fmArray = null;
nmArray = null;
if( typeof( moonDatesMatrix ) == "matrix" )
{
for( ix = 1; ix <= nfm; ix++ )
{
fullMoonDate = moonDatesMatrix[0][ix - 1];
// _TRACEF("ix: %g - fullMoonDate: %s", ix, DateTimeToStr(fullMoonDate));
newMoonDate = moonDatesMatrix[1][ix - 1];
// _TRACEF("ix: %g - nwMoonDate: %s", ix, DateTimeToStr(newMoonDate));
SetArray( ix, &fmArray, fullMoonDate );
SetArray( ix, &nmArray, newMoonDate );
}
}
GraphXSpace = 10;
maxY = HighestVisibleValue( H ) * 1.01;
Plot( Close, "Price", colorBlack, styleCandle | styleNoTitle );
PlotShapes( iif( isFinite( fmArray ), shapeCircle, null ), colorGold, 0, maxY );
PlotShapes( iif( isFinite( nmArray ), shapeSmallCircle, null ), colorLightOrange, 0, maxY );
_N( Title = StrFormat( "{{NAME}} - " + FullName() +
" - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) Vol " +
WriteVal( V, 1.0 ) + " {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
dtSelected = SelectedValue( dt );
fmSelected = SelectedValue( fmArray );
if( fmSelected )
Title = Title + " - Full Moon on " + WriteIf( fmSelected != dtSelected, EncodeColor( colorRed ), "" ) + DateTimeToStr( fmSelected );
nmSelected = SelectedValue( nmArray );
if( nmSelected )
Title = Title + " - New Moon on " + WriteIf( nmSelected != dtSelected, EncodeColor( colorRed ), "" ) + DateTimeToStr( nmSelected );
Filter = 1;
AddColumn( fmArray, "Full Moon Array", formatDateTimeISO );
AddColumn( nmArray, "New Moon Array", formatDateTimeISO );
The red text in the title means that the "shape" is not on the calculated "moon" day, but on the following trading day
I imagine there is room for improvement (and/or corrections), but I'll leave that up to you or other users who are interested in the phases of the moon!