Skip to content

Documentation / Usage

Lottery Classes

Pactole exposes lottery-handling classes in pactole.lottery:

  • BaseLottery: Generic implementation for draw-day handling, combination creation, and history search.
  • EuroMillions: Preconfigured lottery (Tuesday, Friday) using EuroMillionsCombination.
  • EuroDreams: Preconfigured lottery (Monday, Thursday) using EuroDreamsCombination.

In day-to-day usage, a lottery object is the main entry point to:

  • Plan plays around next draw dates.
  • Build valid game-specific combinations.
  • Inspect cached historical draws.
  • Search past results for winning ranks.

Create lottery instances

Use built-in classes when your game rules match EuroMillions or EuroDreams.

from pactole import EuroDreams, EuroMillions

euromillions = EuroMillions()
eurodreams = EuroDreams()

print(euromillions.draw_days.days)
print(eurodreams.draw_days.days)

Get last and next draw dates

Lottery instances delegate date computations to configured draw days.

This is typically used to decide if a ticket should be registered for the current draw or for the next one.

from datetime import date

from pactole import EuroMillions

lottery = EuroMillions()
today = date(2026, 2, 19)

print(lottery.get_last_draw_date(from_date=today, closest=True))
print(lottery.get_next_draw_date(from_date=today, closest=True))

Create and generate tickets from a lottery

Use the lottery API to keep code generic, even if you later swap provider or game type.

from pactole import EuroMillions

lottery = EuroMillions()

played = lottery.get_combination(numbers=[3, 15, 22, 28, 44], stars=[2, 9])
suggested = lottery.generate(n=3)
spread_suggested = lottery.generate(n=12, partitions=4)

print(played)
for ticket in suggested:
    print(ticket)

print(len(spread_suggested))

partitions lets you spread generated tickets across slices of the full combination rank space. This is useful when generating larger batches.

Work with historical draw records

Lottery classes expose helper methods backed by their configured provider.

force=True refreshes source data before reading.

from pactole import EuroMillions

lottery = EuroMillions()
ticket = lottery.get_combination(numbers=[3, 15, 22, 28, 44], stars=[2, 9])

print(lottery.count())

raw_rows = lottery.dump(force=False)
print(raw_rows[:1])

records = list(lottery.get_records())
matches = list(lottery.find_records(ticket))

print(len(records))
for found in matches[:3]:
    print(found.record.draw_date, found.rank)

Understand returned models

get_records() yields DrawRecord instances. A record includes:

  • period, draw_date, deadline_date
  • combination (lottery-specific combination object)
  • numbers (component values keyed by component name)
  • winning_ranks (list of WinningRank dataclasses)

find_records() yields FoundCombination instances. Each one includes:

  • record (DrawRecord)
  • rank (winning rank for your query)
  • match (CompoundCombination representing the matched values)
from pactole import EuroMillions

lottery = EuroMillions()
ticket = lottery.get_combination(numbers=[3, 15, 22, 28, 44], stars=[2, 9])

records = list(lottery.get_records())
found = list(lottery.find_records(ticket))

record = records[0]
print(type(record).__name__, record.draw_date, record.combination.to_dict())

if found:
    first = found[0]
    print(type(first).__name__, first.rank, first.match.to_string())

DrawRecord serialization

Use these helpers depending on target format:

  • record.to_csv(): flat export with component_index fields and rank fields (for example numbers_1, stars_2, numbers_rank, combination_rank, rank_1_gain).
  • record.to_dict() / record.to_json(): API-friendly dictionary with:
    • combination: serialized combination payload
    • numbers: flattened list (combination.values)
    • winning_ranks: list of {rank, winners, gain}

Important validation rule:

  • DrawRecord.from_dict() and DrawRecord.from_json() validate that numbers == combination.values and raise ValueError if they differ.

FoundCombination serialization

  • found.to_csv() stores match as a string (via to_string()).
  • found.to_dict() / found.to_json() store match as a dictionary (via dump()).

Build a Pandas DataFrame from raw export

Use dump() when you want tabular analysis (filtering, grouping, charts, exports).

import pandas as pd

from pactole import EuroMillions

lottery = EuroMillions()
raw_rows = lottery.dump(force=False)

df = pd.DataFrame(raw_rows)
df["draw_date"] = pd.to_datetime(df["draw_date"])

print(df.shape)
print(df[["draw_date", "numbers_1", "numbers_2", "stars_1", "stars_2"]].head())

# Example: average rank-1 gain by year
df["year"] = df["draw_date"].dt.year
avg_rank1_gain = df.groupby("year", as_index=False)["rank_1_gain"].mean()
print(avg_rank1_gain.tail())

Search with rank constraints

Use min_rank and max_rank to filter matches by prize category.

  • Without strict, bounds are inclusive.
  • With strict=True, bounds are exclusive, so only ranks strictly between min_rank and max_rank are returned.
  • If you pass only min_rank, the upper bound defaults to the combination's maximum winning rank.
  • If you pass only max_rank, the lower bound defaults to the combination's minimum winning rank.
from pactole import EuroMillions

lottery = EuroMillions()
ticket = lottery.get_combination(numbers=[3, 15, 22, 28, 44], stars=[2, 9])

ranks_1_to_4 = list(lottery.find_records(ticket, min_rank=1, max_rank=4))
exactly_rank_4 = list(lottery.find_records(ticket, min_rank=4, max_rank=4))
strictly_between_1_and_5 = list(
        lottery.find_records(ticket, min_rank=1, max_rank=5, strict=True)
)

print(len(ranks_1_to_4), len(exactly_rank_4), len(strictly_between_1_and_5))

Build your own lottery class

Subclass BaseLottery by passing a configured provider to super().__init__.

from pactole.combinations import EuroMillionsCombination
from pactole.data.providers import FDJProvider
from pactole.lottery import BaseLottery


class CustomLottery(BaseLottery):
    def __init__(self) -> None:
        super().__init__(
            provider=FDJProvider(
                "euromillions-my-million",
                draw_days=["MONDAY", "THURSDAY"],
                combination_factory=EuroMillionsCombination,
                cache_name="custom_lottery",
            )
        )

Create a specific provider class first

For reusable setups, create a provider class dedicated to one source/game configuration, then inject it into your lottery class.

from pactole.combinations import EuroMillionsCombination
from pactole.data.providers import FDJProvider
from pactole.lottery import BaseLottery


class EuroMillionsFDJProvider(FDJProvider):
    def __init__(self) -> None:
        super().__init__(
            resolver="euromillions-my-million",
            draw_days=["TUESDAY", "FRIDAY"],
            draw_day_refresh_time="22:00",
            combination_factory=EuroMillionsCombination,
            cache_name="euromillions_fdj_custom",
        )


class EuroMillionsCustomLottery(BaseLottery):
    def __init__(self) -> None:
        super().__init__(provider=EuroMillionsFDJProvider())

Create a fully custom provider from resolver + parser

If your archive source is not FDJ, create a provider by composing your own resolver and parser.

from pactole.combinations import EuroMillionsCombination
from pactole.data import BaseParser, BaseProvider, BaseResolver, DrawRecord


class CustomResolver(BaseResolver):
    def _load_cache(self) -> dict[str, str]:
        return {
            "draws_2026.zip": "https://local.test/lottery/draws_2026.zip",
        }


class CustomParser(BaseParser):
    def __call__(self, data: dict) -> DrawRecord:
        # Parse one source row into a DrawRecord.
        # Implement mapping according to your data schema.
        ...


class CustomProvider(BaseProvider):
    def __init__(self) -> None:
        super().__init__(
            resolver=CustomResolver(),
            parser=CustomParser(combination_factory=EuroMillionsCombination),
            draw_days=["TUESDAY", "FRIDAY"],
            combination_factory=EuroMillionsCombination,
            cache_name="custom_provider",
        )

Configure built-in lotteries with environment variables

EuroMillions and EuroDreams can be customized without subclassing.

  • PACTOLE_CACHE_ROOT, FDJ_ARCHIVES_PAGE_URL
  • EUROMILLIONS_PROVIDER_CLASS, EURODREAMS_PROVIDER_CLASS
  • EUROMILLIONS_DRAW_DAYS, EURODREAMS_DRAW_DAYS
  • EUROMILLIONS_DRAW_DAY_REFRESH_TIME, EURODREAMS_DRAW_DAY_REFRESH_TIME
  • EUROMILLIONS_CACHE_NAME, EURODREAMS_CACHE_NAME
  • EUROMILLIONS_ARCHIVES_PAGE, EURODREAMS_ARCHIVES_PAGE

For defaults and precedence rules, see Environment variables.

Example:

export PACTOLE_CACHE_ROOT="pactole_local"
export EUROMILLIONS_DRAW_DAYS="MONDAY,THURSDAY"
export EUROMILLIONS_CACHE_NAME="euromillions_local"