Try Astrologer API

Subscribe to support and grow the project.

Retrograde Stations #

The RetrogradeStationFactory finds planetary stations – the moments a planet turns retrograde (SR) or direct (SD) – within a date range, ordered chronologically. Dates are ISO strings treated as UTC. The default planet set is Mercury through Pluto.

Basic Usage #

from kerykeion import RetrogradeStationFactory

result = RetrogradeStationFactory.from_iso_range("2026-01-01", "2026-12-31")
for station in result.stations:
    print(station.iso_utc, station.planet, station.station_type)  # station_type: 'SR' (turns retrograde) / 'SD' (turns direct)

Restrict to specific planets:

mercury = RetrogradeStationFactory.from_iso_range("2026-01-01", "2026-12-31", planets=["Mercury"])

For sidereal output, provide an ayanamsha. A station is the instant a planet’s longitudinal speed crosses zero, so its time is zodiac-independent; the reported longitude and sign use the requested zodiac.

sidereal = RetrogradeStationFactory.from_iso_range(
    "2026-01-01",
    "2026-12-31",
    zodiac_type="Sidereal",
    sidereal_mode="LAHIRI",
)

Methods #

from_iso_range(start_date, end_date, planets=None, zodiac_type="Tropical", sidereal_mode=None) #

Parameter Type Default Description
start_date str (ISO) Range start (a date-only value starts at 00:00 UTC).
end_date str (ISO) Range end (a date-only value is widened through the end of that UTC day).
planets list[str] or None None Subset of planet names. Defaults to Mercury–Pluto; "Chiron" is accepted on request. Any other name (the Sun and the Moon included, since they never station) raises ValueError.
zodiac_type ZodiacType "Tropical" "Tropical" or "Sidereal"; affects reported longitude/sign, not station time.
sidereal_mode SiderealMode or None None Required ayanamsha when zodiac_type="Sidereal".

Returns: RetrogradeStationsCollectionModel

from_julian_day(start_jd, end_jd, planets=None, zodiac_type="Tropical", sidereal_mode=None) #

Same as above with Julian Day (UT) bounds. Raises KerykeionException for an invalid zodiac configuration or if the ephemeris backend fails mid-scan, and ValueError for an unknown planet name, a non-finite Julian bound, or an over-large range.

Data Models #

RetrogradeStationsCollectionModel #

Field Type Description
start_jd float Requested Julian Day (UT) range start.
end_jd float Requested Julian Day (UT) range end.
stations list Chronologically ordered stations.

Each StationModel item has:

Field Type Description
planet str Planet that stationed.
station_type str "SR" (turns retrograde) or "SD" (turns direct).
iso_utc str ISO 8601 UTC datetime of the exact station.
julian_day float Julian Day (UT) of the station.
sign str Zodiac sign at the station.
sign_num int Sign index (0–11).
degree float Degree within the sign (0–30).
ecliptic_longitude float Absolute ecliptic longitude (0–360).

Retrograde Periods #

Stations answer when did the motion turn?. Retrograde periods answer was this planet retrograde on that date? – the spans of retrograde motion themselves, so a planet that neither turned retrograde nor turned direct inside the range is still reported, with both bounds flagged as the range edges. A retrograde station opens a span, a direct station closes it, and the range clips whatever it cuts.

from kerykeion import RetrogradeStationFactory

periods = RetrogradeStationFactory.retrograde_periods_from_iso_range(
    "2026-01-01", "2026-12-31", planets=["Mercury"]
)
for period in periods.periods:
    print(period.planet, period.start, "->", period.end, period.start_clipped, period.end_clipped)
# Mercury 2026-02-26T06:48:10Z -> 2026-03-20T19:32:50Z False False
# Mercury 2026-06-29T17:35:55Z -> 2026-07-23T22:57:51Z False False
# Mercury 2026-10-24T07:12:42Z -> 2026-11-13T15:53:52Z False False

"Chiron" is opt-in here too, and its slow retrograde arcs show the clipping:

chiron = RetrogradeStationFactory.retrograde_periods_from_iso_range(
    "2026-01-01", "2026-12-31", planets=["Chiron"]
)
first = chiron.periods[0]
print(first.start, first.start_clipped)  # 2026-01-01T00:00:00Z True

retrograde_periods_from_iso_range(start_date, end_date, planets=None, zodiac_type="Tropical", sidereal_mode=None) #

Same parameters as from_iso_range. Returns: RetrogradePeriodsCollectionModel.

retrograde_periods_from_julian_day(start_jd, end_jd, planets=None, zodiac_type="Tropical", sidereal_mode=None) #

Same as above with Julian Day (UT) bounds. Raises the same KerykeionException / ValueError as from_julian_day, plus a KerykeionException when the stations of one planet do not alternate (a retrograde station while already retrograde, or a direct station while direct).

RetrogradePeriodsCollectionModel #

Field Type Description
start_jd float Requested Julian Day (UT) range start.
end_jd float Requested Julian Day (UT) range end.
periods list The RetrogradePeriodModel spans, planet by planet.

Each RetrogradePeriodModel item has:

Field Type Description
planet str Planet the span belongs to.
start_jd float Julian Day (UT) the retrograde motion begins, clipped to the range start.
end_jd float Julian Day (UT) the retrograde motion ends, clipped to the range end.
start str ISO 8601 UTC of start_jd.
end str ISO 8601 UTC of end_jd.
start_clipped bool True when the planet was already retrograde at the range start.
end_clipped bool True when the planet is still retrograde at the range end.

A period carries no sign: a station instant is the same in every zodiac, so zodiac_type and sidereal_mode change nothing in the output beyond being validated.

Clipping and the range edges. A clipped bound says where the range cut the span, not where the real station is: nothing is searched outside the range except a single edge probe. That probe reaches a solver’s resolution – 50 ms, ten times the bisection’s own error and twenty times finer than the second the ISO strings resolve – past either bound, so a range whose bound is a station instant this library reported behaves as intended: a station on the range start sets the initial motion (rather than the sign of a speed that is numerically zero there) and a station on the range end closes its span unclipped. A station any farther from a bound, however close, is a real station. Spans within the range are exactly the instants from_julian_day reports for the same range. An empty or inverted range yields no periods, and only a zero-length span is dropped.