Horoscope object
The data structures of the six scopes, plus palace lookups that need not be handed the astrolabe again.
A horoscope projects the natal chart onto a point in time. The same chart shows a different palace layout in different years — which is exactly what "the decadal scope has moved to that palace" means.
h = chart.horoscope("2025-6-1", 0)Horoscope holds the natal chart that produced it, so none of the query methods need the astrolabe
passed in again. The trailing astrolabe=None parameter on every query method is there for the case
where you hold horoscope data but keep the chart elsewhere; day to day you never pass it.
The examples on this page all start from an en-US natal chart, so the display values in the output
are the English translations.
Fields
| Field | Type | Description |
|---|---|---|
solar_date | str | The target solar date, as passed in |
lunar_date | str | The target date as a Chinese lunar date string |
decadal age yearly monthly daily hourly | See below | The six horoscope scopes |
solar_date is the target date, not the birth date; the birth date is on the natal chart, reachable
as h.astrolabe().solar_date.
The six scopes
| Field | Type | Span | Description |
|---|---|---|---|
decadal | HoroscopeItem | Ten years | The decadal scope; the childhood scope for the years before it begins |
age | AgeItem | One year | The age scope, moving one palace per nominal year |
yearly | HoroscopeYearly | One year | The yearly scope, its palace fixed by the year's pillar |
monthly | HoroscopeItem | One month | The monthly scope |
daily | HoroscopeItem | One day | The daily scope |
hourly | HoroscopeItem | One double-hour | The hourly scope |
The age scope versus the yearly scope
Both advance once per year, but they start differently: the age scope starts from the birth-year branch and steps forward with the nominal age, while the yearly scope simply asks which palace that year's pillar falls in. The two lines are independent, and Zi Wei practice usually reads them together.
HoroscopeItem
| Field | Type | Description |
|---|---|---|
index | int | Which palace this scope lands on (a palace index) |
name | str | Display name of the scope, translated into the output language |
name_key | str | Scope key: decadal / childhood (before the decadals begin) / turn (age fortune) / yearly / monthly / daily / hourly. Predicate on this, never on the translation |
heavenly_stem / heavenly_stem_key | str | Stem of the scope, which determines the mutagens it flies |
earthly_branch / earthly_branch_key | str | Branch of the scope |
palace_names / palace_name_keys | list[str] | The twelve palace names re-derived with this scope's palace as the Soul palace, indexed by palace index |
mutagen / mutagen_star_keys | list[str] | The stars this scope's stem transforms, in the order lu, quan, ke, ji; mutagen_star_keys holds the mutated stars' star keys, synonymous with the palace field of the same name |
stars | list[list[Star]] | None | The scope stars of this layer; None for layers that have none |
AgeItem and HoroscopeYearly both inherit HoroscopeItem and each add one field:
| Type | Extra field | Description |
|---|---|---|
AgeItem | nominal_age: int | The nominal age at that date |
HoroscopeYearly | yearly_dec_star: YearlyDecStar | The yearly Sui-qian and Jiang-qian gods |
class YearlyDecStar:
jiangqian12: list[str] # translated yearly Jiang-qian gods, indexed by palace index
jiangqian12_keys: list[str] # the matching keys
suiqian12: list[str] # translated yearly Sui-qian gods
suiqian12_keys: list[str] # the matching keysBecause this is inheritance rather than wrapping, the shared fields are reached directly: write
h.yearly.heavenly_stem, with no .base layer as on the Rust side.
h = chart.horoscope("2025-6-1", 0)
print(h.yearly.heavenly_stem, h.yearly.earthly_branch, h.age.nominal_age)
print(h.yearly.yearly_dec_star.suiqian12[:3])
print(h.yearly.yearly_dec_star.jiangqian12_keys[:3])Output
yi si 26
['blessed', 'sorrowing', 'illness']
['jiesha', 'zhaisha', 'tiansha']Example
h = chart.horoscope("2025-6-1", 0)
for item in (h.decadal, h.monthly, h.daily, h.hourly):
print(f"{item.name} lands on palace {item.index} with pillar {item.heavenly_stem}{item.earthly_branch}")
print("age scope nominal age", h.age.nominal_age)
print("decadal mutagens", h.decadal.mutagen)Output
decadal lands on palace 2 with pillar gengchen
monthly lands on palace 3 with pillar renwoo
daily lands on palace 8 with pillar xinchou
hourly lands on palace 8 with pillar wuzi
age scope nominal age 26
decadal mutagens ['sun', 'general', 'moon', 'fortunate']age_palace
Purpose Get the palace the age scope occupies this year.
Zi Wei meaning The age scope is a line advancing year by year; whichever palace it lands on becomes the focus for that year.
Signature
def age_palace(self, astrolabe: Astrolabe | None = None) -> Palace | NoneParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
astrolabe | Astrolabe | None | No | None | Usually omitted; the horoscope already holds the natal chart |
Return value Palace | None — a palace on the natal chart.
The horoscope already holds the natal chart, so in practice this is never None; only a hand-built
horoscope object bound to no chart and given no astrolabe comes up empty.
Example
h = chart.horoscope("2025-6-1", 0)
print(h.age_palace().name)Output
propertypalace
Purpose Get one of the twelve palaces as re-derived under a given horoscope scope.
Zi Wei meaning Once the decadal scope reaches a palace, the twelve palaces are re-anchored with that palace as the "decadal Soul palace". "The decadal Spouse palace" refers to that re-anchored naming, and it is usually not the same palace as the natal Spouse palace.
Signature
def palace(
self,
name: PalaceName | str,
scope: Scope | ScopeLiteral,
astrolabe: Astrolabe | None = None,
) -> Palace | NoneParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | Yes | — | The palace-name key to fetch |
scope | str | Yes | — | Which scope's twelve palaces to search |
astrolabe | Astrolabe | None | No | None | Usually omitted |
Return value Palace | None — a palace on the natal chart (the same cell carries different names
under different scopes). With "origin" as the scope, these are the natal twelve palaces. A
misspelled palace name or scope key returns None rather than raising.
Example
from x_iztro import PalaceName, Scope
h = chart.horoscope("2025-6-1", 0)
print("the decadal Soul palace is the natal", h.palace(PalaceName.SOUL, Scope.DECADAL).name)
print("the natal Soul palace is", h.palace(PalaceName.SOUL, Scope.ORIGIN).name)Output
the decadal Soul palace is the natal spouse
the natal Soul palace is soulEdge cases and pitfalls
What comes back is the cell on the natal chart
On the palace object returned by palace("soulPalace", "decadal"), name is still the natal palace
name (Spouse in the example), because it is that cell on the natal chart. To see what the cell is
called at the decadal layer, read h.decadal.palace_names[index].
surround_palaces
Purpose Get the surrounded palaces of a palace under a given horoscope scope.
Signature
def surround_palaces(
self,
name: PalaceName | str,
scope: Scope | ScopeLiteral,
astrolabe: Astrolabe | None = None,
) -> SurroundedPalaces | NoneParameters Same as palace.
Return value SurroundedPalaces | None; its predicates are on
Surrounded palaces.
Example
h = chart.horoscope("2025-6-1", 0)
sp = h.surround_palaces(PalaceName.WEALTH, Scope.YEARLY)
print("the surrounded set of the yearly Wealth palace is anchored on the natal", sp.target.name)Output
the surrounded set of the yearly Wealth palace is anchored on the natal healthhas_horoscope_stars / has_one_of_horoscope_stars / not_have_horoscope_stars
Purpose Test whether a palace under a given scope holds the given scope stars.
Zi Wei meaning Scope stars are a group produced by each horoscope layer: Tiankui, Tianyue, Wenchang, Wenqu, Lucun, Qingyang, Tuoluo, Tianma, Hongluan and Tianxi. They carry different names in different layers — Yunkui and Yunyue at the decadal layer, Liukui and Liuyue at the yearly layer — with the same meaning applied to their own time span.
Signature
def has_horoscope_stars(self, name, scope, stars: list[str], astrolabe=None) -> bool
def has_one_of_horoscope_stars(self, name, scope, stars: list[str], astrolabe=None) -> bool
def not_have_horoscope_stars(self, name, scope, stars: list[str], astrolabe=None) -> boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | Yes | — | The palace-name key under that scope |
scope | str | Yes | — | The horoscope scope |
stars | list[str] | Yes | — | Scope star keys, which must use that layer's names |
astrolabe | Astrolabe | None | No | None | Usually omitted |
Return value
| Method | Meaning |
|---|---|
has_horoscope_stars | All of them are present |
has_one_of_horoscope_stars | At least one is present |
not_have_horoscope_stars | None is present |
Example
h = chart.horoscope("2025-6-1", 0)
print(h.has_horoscope_stars(PalaceName.SOUL, Scope.DECADAL, ["yunlu"]))
print(h.has_one_of_horoscope_stars(PalaceName.SOUL, Scope.DECADAL, ["yunlu", "yunyang"]))
print(h.not_have_horoscope_stars(PalaceName.SOUL, Scope.DECADAL, ["yuntuo"]))Output
False
False
TrueEdge cases and pitfalls
has_horoscope_mutagen
Purpose Test whether a palace under a given scope carries a mutagen flown by that scope's stem.
Zi Wei meaning Every horoscope layer has a stem of its own, and it transforms four stars just as the birth-year stem does. A question like "does the decadal lu land in the decadal Wealth palace?" is asking about this.
Signature
def has_horoscope_mutagen(self, name, scope, mutagen: Mutagen, astrolabe=None) -> boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | Yes | — | The palace-name key under that scope |
scope | str | Yes | — | The horoscope scope |
mutagen | str | Yes | — | A mutagen key |
astrolabe | Astrolabe | None | No | None | Usually omitted |
Return value bool.
Example
from x_iztro import Mutagen
h = chart.horoscope("2025-6-1", 0)
print(h.has_horoscope_mutagen(PalaceName.SOUL, Scope.DECADAL, Mutagen.LU))
print(h.decadal.mutagen)Output
False
['sun', 'general', 'moon', 'fortunate']The decadal stem is geng, and geng sends lu to Taiyang, quan to Wuqu, ke to Taiyin and ji to Tiantong.
Edge cases and pitfalls
Always False when scope is origin
The natal layer has no "layer stem" — the birth-year mutagens are already stamped on the stars' own
mutagen_key. has_horoscope_mutagen(name, "origin", m) therefore returns False outright, which
does not mean the natal chart lacks that mutagen. For natal mutagens use the palace's
has_mutagen.
Only the major and minor stars of the target palace are checked; adjective stars are not.
scope_item / astrolabe
Purpose Get the HoroscopeItem for a scope key, or get back to the natal chart.
Signature
def scope_item(self, scope: Scope | ScopeLiteral) -> HoroscopeItem | None
def astrolabe(self) -> Astrolabe | NoneReturn value scope_item returns None for the scope "origin" — the natal chart is not a
horoscope layer.
Example
h = chart.horoscope("2025-6-1", 0)
print(h.scope_item(Scope.DECADAL).name)
print(h.scope_item(Scope.ORIGIN))
print(h.astrolabe().solar_date)Output
decadal
None
2000-8-16Edge cases and pitfalls
scope_item is for writing generic logic parameterized by scope, which is tidier than a chain of
if scope == ....
HoroscopeItem also carries palace_index_by_name(name), which turns a palace name into a palace
index within that layer's twelve palaces, returning None when there is no match:
h = chart.horoscope("2025-6-1", 0)
item = h.scope_item(Scope.DECADAL)
print(item.palace_index_by_name(PalaceName.SOUL))
print(item.palace_index_by_name(PalaceName.WEALTH))
print(item.palace_index_by_name("nosuch"))Output
2
10
Noneto_text
Purpose The horoscope's semantic text: a complete description for language models and people;
str(h) is equivalent.
Signature
def to_text(self) -> strReturn value str — sectioned plain text in the chart's charting language; each scope carries a
patterns line and flowing-star lines from its own perspective. The full format is on
Semantic text.
Example
h = chart.horoscope("2025-1-1", 0)
print(h.to_text()[:39])Output
=== Horoscope ===
Target Date: 2025-1-1Edge cases and pitfalls
A horoscope constructed detached from a chart (outside horoscope()) has no charting context and
raises ValueError. For the pattern hits as text, see patterns_to_text on
Patterns.
to_dict / to_json
Purpose Export the horoscope as JSON matching the field contract of JS iztro.
Signature
def to_dict(self) -> dict[str, Any]
def to_json(self, **kwargs: Any) -> strShape and usage are the same as
the astrolabe's methods of the same names: to_dict
hands back a deep copy of the underlying DTO and to_json a JSON string, defaulting to
ensure_ascii=False. Do not reach for dataclasses.asdict here either — the horoscope holds a
reference to the natal chart and would recurse forever.
Example
h = chart.horoscope("2025-6-1", 0)
d = h.to_dict()
print(d["solarDate"], d["decadal"]["heavenlyStem"], d["age"]["nominalAge"])
print(sorted(d.keys()))Output
2025-6-1 geng 26
['age', 'daily', 'decadal', 'hourly', 'lunarDate', 'monthly', 'solarDate', 'yearly']