Try Astrologer API

Subscribe to support and grow the project.

Planetary Return Factory #

The PlanetaryReturnFactory calculates the precise moment when a planet returns to its natal position (e.g., Solar Returns). It uses the configured ephemeris backend (libephemeris by default, Swiss Ephemeris optional) for high-precision timing.

What Are Planetary Returns? #

A Planetary Return occurs when a transiting planet returns to the exact degree it occupied at birth. The return chart cast for that precise moment becomes a forecast for the cycle ahead:

Solar Return (Birthday Chart):

  • Happens once per year on or near your birthday
  • The Sun returns to its natal position (~365.25 days)
  • Forecasts themes and events for the coming year
  • Traditionally cast for your current location (relocated solar return)

Lunar Return:

  • Happens approximately every 27-29 days
  • The Moon returns to its natal position
  • Forecasts the emotional climate for the coming month
  • Useful for timing short-term events and emotional cycles

Kerykeion supports Solar, Lunar, Heliocentric, and Lunar Node Crossing returns.

Usage #

Calculating a return involves the Natal Subject and the Return Location (which can differ from birth location).

from kerykeion import AstrologicalSubjectFactory
from kerykeion.planetary_returns.factory import PlanetaryReturnFactory

# 1. Create Natal Subject
natal_subject = AstrologicalSubjectFactory.from_birth_data(
    "Alice", 1990, 6, 15, 12, 0, "London", "GB"
)

# 2. Initialize Calculator (with Return Location)
# Here we calculate the return for New York
return_factory = PlanetaryReturnFactory(
    natal_subject,
    city="New York",
    nation="US",
    online=True
)

# 3. specific Return (e.g., Solar Return for 2024)
solar_return = return_factory.next_return_from_date(
    year=2024,
    month=1,
    day=1, # Start searching from this date
    return_type="Solar"
)

print(f"Return Date: {solar_return.iso_formatted_local_datetime}")
print(f"Sun Position: {solar_return.sun.abs_pos:.2f}°")

Expected Output:

Return Date: 2024-06-14T12:14:42-04:00
Sun Position: 84.09°

Note: The Sun position matches Alice’s natal Sun position exactly (within calculation precision).

Custom Ayanamsa in Returns #

When using sidereal_mode="USER" on the natal subject, pass the custom ayanamsa parameters to PlanetaryReturnFactory so the return chart uses the same ayanamsa:

from kerykeion import AstrologicalSubjectFactory
from kerykeion.planetary_returns.factory import PlanetaryReturnFactory

natal = AstrologicalSubjectFactory.from_birth_data(
    "Sidereal User", 1990, 6, 15, 12, 0,
    lng=-0.1276, lat=51.5074, tz_str="Europe/London",
    online=False,
    zodiac_type="Sidereal",
    sidereal_mode="USER",
    custom_ayanamsa_t0=2451545.0,
    custom_ayanamsa_ayan_t0=23.5,
)

return_factory = PlanetaryReturnFactory(
    natal,
    city="London", nation="GB",
    lng=-0.1276, lat=51.5074, tz_str="Europe/London",
    online=False,
    custom_ayanamsa_t0=2451545.0,
    custom_ayanamsa_ayan_t0=23.5,
)

solar_return = return_factory.next_return_from_date(2024, 1, 1, return_type="Solar")
print(f"Return ayanamsa: {solar_return.ayanamsa_value:.4f}°")

When the natal subject uses sidereal_mode="USER", passing custom_ayanamsa_t0 and custom_ayanamsa_ayan_t0 explicitly overrides the natal’s; leaving them out reads them from the natal subject. The constructor raises KerykeionException only when neither source provides both.

Supported Return Types #

  • "Solar" (Sun) – Yearly forecast.
  • "Lunar" (Moon) – Monthly forecast.
  • "Heliocentric" – when a planet returns to its natal heliocentric longitude (see below).
  • "Lunar_Node_Crossing" – when the Moon crosses its own node (latitude = 0; see below).

Note: return_type values are case-sensitive (e.g. exactly "Solar").

Heliocentric Returns #

next_heliocentric_return(planet_name, start_jd, backwards=False) finds when a planet returns to its natal heliocentric longitude (via helio_cross_ut). The Sun and Moon are not valid targets — the Sun is the heliocentric origin and the Moon’s heliocentric longitude tracks Earth’s orbit — and raise KerykeionException. backwards=True requires the libephemeris backend (pyswisseph cannot search backward).

# Using the `return_factory` from the example above:
helio = return_factory.next_heliocentric_return_from_year("Jupiter", 2026)
print(helio.iso_formatted_utc_datetime)

Convenience wrappers mirror the Solar/Lunar ones: next_heliocentric_return_from_year(planet_name, year), next_heliocentric_return_from_date(planet_name, year, month, day=1, backwards=False), next_heliocentric_return_from_iso_formatted_time(planet_name, iso_formatted_time, backwards=False).

Lunar Node Crossings #

next_lunar_node_crossing(start_jd, backwards=False) finds the next moment the Moon crosses its own node (ecliptic latitude = 0; via mooncross_node_ut). It is zodiac-independent. backwards=True requires the libephemeris backend.

crossing = return_factory.next_lunar_node_crossing_from_year(2026)
print(crossing.iso_formatted_utc_datetime)

Convenience wrappers: next_lunar_node_crossing_from_year(year), next_lunar_node_crossing_from_date(year, month, day=1, backwards=False), next_lunar_node_crossing_from_iso_formatted_time(iso_formatted_time, backwards=False).

Constructor Parameters #

Parameter Type Default Description
subject AstrologicalSubjectModel Required The natal subject whose return is being calculated.
city Optional[str] None City name for the return location.
nation Optional[str] None ISO country code for the return location.
lng int | float | None None Longitude of the return location.
lat int | float | None None Latitude of the return location.
tz_str Optional[str] None Timezone string for the return location.
online bool True Whether to resolve location via GeoNames API.
geonames_username Optional[str] None GeoNames username for online mode.
cache_expire_after_days int 30 Days to cache online location lookups.
altitude int | float | None None Altitude in meters. Topocentric returns use the return location and altitude in both the exact-crossing search and the returned chart.
custom_ayanamsa_t0 Optional[float] None Reference epoch (Julian Day) for USER sidereal mode.
custom_ayanamsa_ayan_t0 Optional[float] None Ayanamsa offset at epoch (required with USER sidereal mode).
active_fixed_stars List[str] | None None Fixed-star catalog names to compute on the return chart.
calculate_dignities bool False Populate essential dignity data.
calculate_nakshatra bool False Populate Vedic nakshatra data.
nakshatra_ayanamsa Optional[SiderealMode] inherited Ayanamsa used to place the nakshatras on a non-sidereal return chart. Passed explicitly it wins — None included, which is the pre-v6 uncorrected division. Left out, the return inherits the mode the natal actually placed its nakshatras with, and falls back to "LAHIRI" when the natal placed none.
calculate_gauquelin bool False Populate Gauquelin sectors.
calculate_nutation bool False Populate the nutation model.
calculate_local_space bool False Populate local-space azimuth/altitude.

Methods #

next_return_from_date(year, month, day=1, *, return_type, backwards=False) #

Finds a return starting from a specific year/month/day. This is the primary method.

result = return_factory.next_return_from_date(2025, 1, 1, return_type="Solar")
Parameter Type Default Description
year int Required Year to start searching from.
month int Required Month to start searching from.
day int 1 Day to start searching from.
return_type SolarLunarReturnType Required "Solar" or "Lunar" (keyword-only).
backwards bool False If True, return the most recent return before the starting date instead of the next one.

next_return_from_iso_formatted_time(iso_formatted_time, return_type, backwards=False) #

Finds a return starting from a precise ISO timestamp.

result = return_factory.next_return_from_iso_formatted_time("2024-06-15T12:00:00", "Lunar")
Parameter Type Default Description
iso_formatted_time str Required ISO 8601 timestamp at which to start the search (naive values are read as UTC).
return_type SolarLunarReturnType Required "Solar" or "Lunar".
backwards bool False If True, search for the most recent return before the timestamp.

Return instants are reported truncated to the whole second, and the search is ordered at that resolution: the instant of a return this factory reported is a valid seed for the following one (or, with backwards=True, the preceding one), so a walk of next_return_from_iso_formatted_time calls seeded with each result’s iso_formatted_utc_datetime visits every return exactly once. A seed inside the same second as a crossing selects the return after it (forward) or before it (backward). The date and year wrappers above keep an inclusive midnight seed: “the first return of this date” includes a return in the date’s first second.

first = return_factory.next_return_from_iso_formatted_time("2024-03-01T00:00:00+00:00", "Solar")
second = return_factory.next_return_from_iso_formatted_time(first.iso_formatted_utc_datetime, "Solar")
assert second.julian_day > first.julian_day  # the following return, not the same one again

Backward Solar/Lunar return search requires the default libephemeris backend; the optional pyswisseph backend does not expose backward crossing searches and raises KerykeionException for backwards=True.

previous_solar_return = return_factory.next_return_from_date(
    2025, 1, 1,
    return_type="Solar",
    backwards=True,
)
print(previous_solar_return.iso_formatted_utc_datetime)

next_return_from_year(year, return_type) (Deprecated) #

Finds the first return occurring in a given calendar year. Deprecated – use next_return_from_date instead.

result = return_factory.next_return_from_year(2025, "Solar")  # Deprecated
result = return_factory.next_return_from_date(2025, 1, 1, return_type="Solar")  # Preferred

Both arguments are required; return_type is "Solar" or "Lunar". The method emits DeprecationWarning, is scheduled for removal in 7.0.0, and delegates to next_return_from_date(year, 1, 1, return_type=...).

next_return_from_month_and_year(year, month, return_type) (Deprecated) #

This older three-argument alias searches from the first day of month. It emits DeprecationWarning, is scheduled for removal in 7.0.0, and delegates to next_return_from_date(year, month, 1, return_type=...).

Relocation Astrology #

Planetary returns are often calculated for the subject’s current location rather than birth location. Simply pass the current residence coordinates/city to the PlanetaryReturnFactory constructor to generate a relocated return chart.