Guides

Derived Charts

The derived layer is the standard astrological derivations computed on top of the validated primitives. These are time mappings and arithmetic on apparent positions that are already checked against Swiss Ephemeris, so they add no new ephemeris. The TypeScript port is pinned to the Python reference by a cross-language golden, and everything here is exported from the package root.

setup.ts
import { Engine, julianDay } from "caelus";
import { embeddedData } from "caelus/data-embedded";

const engine = new Engine(embeddedData);

const natalJd = julianDay(1990, 6, 10, 14, 30, 0);   // UT
const targetJd = julianDay(2026, 6, 13, 0, 0, 0);

Returns

A return is when a body comes back to its natal longitude. Outer-planet returns can show three crossings around a retrograde loop, so the search returns every hit in the window.

returns.ts
import { solarReturn, lunarReturn, returns } from "caelus";

// Next solar return after the 2026 birthday window:
solarReturn(engine, natalJd, targetJd, targetJd + 366);   // [jd, ...]
lunarReturn(engine, natalJd, targetJd, targetJd + 30);

// Any body, e.g. the Saturn return:
returns(engine, "saturn", natalJd, natalJd, natalJd + 365 * 30);
123456789101112ACMCDCIC19°27'12°58'13°04'26°20'16°37'25°52'12°58'2°35'4°13'5°14'℞29°36'2°53'℞
The 2026 solar return: the Sun back at its natal longitude, 2026-06-10 07:28 UT. Built with one chartAt call at the return instant and drawn with ChartWheel.

Secondary progressions and solar arc

Secondary progressions map one day of real motion to one year of life. progressedJd gives the JD whose real positions are the progressed positions; solarArc is the angle the progressed Sun has travelled, applied forward to any natal point.

progressions.ts
import { progressedLongitude, solarArc, directedLongitude } from "caelus";

progressedLongitude(engine, "moon", natalJd, targetJd);   // progressed Moon
solarArc(engine, natalJd, targetJd);                      // arc in degrees
directedLongitude(engine, "mars", natalJd, targetJd);     // solar-arc directed Mars

Composite and Davison

A composite chart takes the shorter-arc midpoint of each body's two longitudes. A Davison chart is a real chart at the midpoint in time and place: davisonParams returns the midpoint instant and coordinates, and you read real positions at them. The returned latitude and longitude are where the houses are cast.

relationship.ts
import { compositeLongitudes, davisonParams } from "caelus";

const bodies = ["sun", "moon", "mercury", "venus", "mars"];
const jdB = julianDay(1988, 2, 2, 9, 15, 0);

compositeLongitudes(engine, natalJd, jdB, bodies);   // { sun: 123.4, ... }

// Davison: the midpoint in time and place, then real positions there.
const [midJd, midLat, midLon] = davisonParams(
natalJd, jdB, 27.95, -82.46, 40.71, -74.01,
);
engine.longitude("sun", midJd);    // Davison Sun longitude (degrees)
engine.position("venus", midJd);   // full position at midLat/midLon

Harmonics and antiscia

The nth-harmonic chart multiplies each longitude by n (wrapped to 360). Antiscia reflect a point across the solstice or equinox axis.

harmonics.ts
import { harmonicChart, antiscion, contraAntiscion } from "caelus";

harmonicChart(engine, natalJd, ["sun", "moon", "venus"], 5);   // 5th harmonic

antiscion(15);        // reflection across 0° Cancer / 0° Capricorn
contraAntiscion(15);  // reflection across 0° Aries / 0° Libra

Declination: aspects and out of bounds

Parallels and contraparallels are declination aspects (same or opposite declination within orb). A body is out of bounds when its declination exceeds the obliquity of the ecliptic.

declination.ts
import { declinationAspects, outOfBounds, outOfBoundsMargin } from "caelus";

declinationAspects(engine, ["sun", "moon", "mars"], natalJd);
// [{ a: "sun", b: "mars", kind: "parallel" }, ...]

outOfBounds(engine, "moon", natalJd);        // true | false
outOfBoundsMargin(engine, "moon", natalJd);  // degrees past the obliquity
sun+23.02°
moon-24.93°out of bounds
mercury+17.27°
venus+13.83°
mars+1.18°
jupiter+22.79°
saturn-21.14°
uranus-23.50°out of bounds
neptune-21.85°
pluto-1.25°
chiron+16.29°
mean_node-17.75°
true_node-18.24°
Parallels (∥) and contraparallels (⊼), within 1°:
  • sun jupiter (parallel)
  • sun uranus (contraparallel)
  • mercury chiron (parallel)
  • mercury mean_node (contraparallel)
  • mercury true_node (contraparallel)
  • mars pluto (contraparallel)
  • jupiter uranus (contraparallel)
  • jupiter neptune (contraparallel)
  • saturn neptune (parallel)
  • mean_node true_node (parallel)
Each body’s declination, with out-of-bounds (beyond the obliquity, ±23.4°) flagged, and the parallels and contraparallels among them.

Dignities and sect

Essential dignities classify a body in a sign as domicile, exaltation, detriment, or fall. Sect is the day/night division: whether the Sun is above the horizon, and whether a planet is in its preferred sect.

dignities.ts
import { dignityOf, isDayChart, inSect } from "caelus";

dignityOf(engine, "mars", natalJd);   // ["domicile"] in Aries/Scorpio, etc.

const day = isDayChart(engine, natalJd, 27.95, -82.46);
inSect("jupiter", day);   // true when Jupiter is in sect (diurnal by day)

For the traditional five-fold scoring, dignityScore applies William Lilly's weights (rulership +5, exaltation +4, triplicity +3, term +2, face +1; detriment -5, fall -4) and reports the breakdown, the running total, and a peregrine flag. almuten returns the planet with the greatest positive dignity at a degree. The triplicity, term (Egyptian bounds), and face tables are each pinned to a named authority.

dignity-score.ts
import { dignityScore, almuten } from "caelus";

dignityScore("mars", 5, "night");   // Mars at 5° Aries, night chart
// { rulership: 5, triplicity: 3, term: 0, ... total: 8, peregrine: false }

almuten(5);   // { planet: "mars", score: 8 }, strongest at that degree

Timelines

The derivations compose into timelines: the returns search is already a span, and the progression mappings are pure functions of a target JD, so you sample them on whatever cadence you need. A JD converts back to a date with the standard epoch offset, no helper required.

timelines.ts
import { solarReturn, progressedLongitude } from "caelus";

const SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"];
const toDate = (jd: number) =>
new Date((jd - 2440587.5) * 86400000).toISOString().slice(0, 10);

// A decade of solar returns:
const from = julianDay(2026, 1, 1, 0, 0, 0);
solarReturn(engine, natalJd, from, from + 365.25 * 10).map(toDate);
// ["2026-06-10", "2027-06-11", ...]
//
// rendered below from this exact call:

// Progressed-Moon sign at each birthday for the next twelve years:
for (let year = 2026; year < 2038; year++) {
const jd = julianDay(year, 6, 10, 0, 0, 0);
const lon = progressedLongitude(engine, "moon", natalJd, jd);
console.log(year, SIGNS[Math.floor(lon / 30)]);
}
2026-06-102027-06-102028-06-092029-06-102030-06-102031-06-102032-06-092033-06-092034-06-102035-06-10
Ten years of solar returns from 2026-01-01: the search is a span, so a timeline is one call.

The same shape drives a transit timeline (sample engine.longitude over a range) or a directed-aspect scan (directedLongitude against natal points). For century-scale sweeps that evaluate a longitude tens of thousands of times, the turbo tier finds candidate instants fast. For ready-made transit and event-search recipes, see Recipes.

See the API Reference for every signature, Edge Cases & Stability for range and stability notes, and the Changelog for what shipped in 0.7.0.

Start building

Quickstart →