Skip to content

feat: Compose groups via include-groups for reusable tabs and summaries#332

Open
vkryukov wants to merge 1 commit intoachannarasappa:masterfrom
vkryukov:include-groups
Open

feat: Compose groups via include-groups for reusable tabs and summaries#332
vkryukov wants to merge 1 commit intoachannarasappa:masterfrom
vkryukov:include-groups

Conversation

@vkryukov
Copy link

First off, thank you for creating ticker — I use it daily and it’s been incredibly useful. Also, apologies for the unsolicited PR; I hope this enhancement aligns with the project’s direction.

Summary

  • Adds include-groups to group configs so one group can reuse other groups’ watchlists and holdings without copy‑pasting.
  • Enables composite tabs and combined summaries (e.g., “personal holding” = “crypto” + “personal stocks”).
  • Supports nesting and forward references (a composite can include groups defined later).
  • Errors on unknown or cyclic includes; preserves declaration order.

Why

I often want a “summary” group that combines a few existing groups (e.g., crypto + personal stocks) without duplicating config. This adds that capability while remaining backward compatible and predictable.

Config Usage

  • New key: include-groups
  • Effective behavior for a composite group:
    • Watchlist = [included groups’ watchlists in include order] + [this group’s watchlist], de‑duplicated by first occurrence.
    • Holdings = [included groups’ holdings in include order] + [this group’s holdings]; aggregation by symbol remains as today for summaries.
    • You can include default (from top-level watchlist/lots).
    • Cyclic/unknown includes are reported as config errors.

Implementation

  • Schema: Add IncludeGroups []string to ConfigAssetGroup.
  • Validation: Check for duplicate group names; detect unknown/cyclic includes.
  • Resolution: Recursively expand include-groups, then:
    • De‑duplicate the resulting watchlist (first‑seen), leave holdings concatenated (existing aggregation applies).
  • Refactor: Extract small unexported helpers for clarity:
    • resolveGroupIncludes, dedupePreserveOrder, symbolsBySource.
  • UI/printing: No changes required — they use the effective group data.
  • Docs: Update README with example, rules, and note on forward references.
  • Tests: Add cases for composition, unknown/cyclic includes, and forward‑declared groups.

Notes

  • Backward compatible: Existing configs work unchanged.
  • Order: Final group order follows the config file; within a composite, included groups’ items come first, then the group’s own.
  • Performance: Simple DFS per composite group; negligible overhead at startup.

Thank you again for ticker and for considering this improvement. Happy to adjust naming, error messages, or structure to better fit the project’s style.

- Support include-groups in ConfigAssetGroup to compose groups without duplication
- Validate duplicate group names; detect and error on unknown or cyclic includes
- Build effective groups by expanding includes, deduping watchlist (first-seen), concatenating holdings
- Refactor getGroups: extract resolveGroupIncludes, dedupePreserveOrder, symbolsBySource helpers for clarity
- Update README with examples, rules, and forward reference note
- Add CLI tests for composition, unknown includes, cycles, and forward-referenced groups
Copy link
Owner

@achannarasappa achannarasappa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR! This looks like it could be really useful

Some overall comments below:

  • I have a few smaller stylistics comments which I've added inline

  • I found that adding duplicates of the same group also included a duplicate of that group which isn't a problem for watchlist but does duplicate holdings. Example config snippet to duplicate:

  - name: stocks-1
    include-groups:
      - stocks-2
      - stocks-2
      - stocks-2
    holdings:
      - symbol: "LMND"
        quantity: 1.0
        unit_cost: 100
  - name: stocks-2
    holdings:
      - symbol: "LMND"
        quantity: 1.0
        unit_cost: 100
  • Is user sort order preserved and deterministic when including groups?

Name string `yaml:"name"`
Watchlist []string `yaml:"watchlist"`
Holdings []Lot `yaml:"holdings"`
IncludeGroups []string `yaml:"include-groups"`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest changing the name to just includes with the group aspect being implied

* If top level `watchlist` or `lots` properties are defined in the configuration file, the entries there will be added to a group named `default` which will always be shown first
* Ordering is defined by order in the configuration file
* The `holdings` property replaces `lots` under `groups` but serves the same purpose
* New: `include-groups` allows composing a group from other groups without duplicating entries. The effective watchlist is the concatenation of included groups’ watchlists (then the group’s own), de-duplicated by first occurrence; holdings are concatenated (lots aggregate by symbol automatically in summaries)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed to call out this is new and it may not be new as time goes on.

* The `holdings` property replaces `lots` under `groups` but serves the same purpose
* New: `include-groups` allows composing a group from other groups without duplicating entries. The effective watchlist is the concatenation of included groups’ watchlists (then the group’s own), de-duplicated by first occurrence; holdings are concatenated (lots aggregate by symbol automatically in summaries)

Example composite group:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nice to include this in a collapsible secion since this likely may not be used by the majority of users:

https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections

// Build final groups in declaration order using flattened config
for _, configAssetGroup := range configAssetGroups {
// compute effective watchlist/holdings
effWatchlist := configAssetGroup.Watchlist
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: full word names

// Index groups by name for include resolution and validate duplicate names
groupsByName := make(map[string]c.ConfigAssetGroup)
for _, g := range configAssetGroups {
if g.Name == "" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: full word names

// resolveGroupIncludes flattens include-groups recursively preserving order and detecting cycles
func resolveGroupIncludes(groupsByName map[string]c.ConfigAssetGroup, cur c.ConfigAssetGroup, visiting map[string]bool) ([]string, []c.Lot, error) {

wl := make([]string, 0)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: full word names

@vkryukov
Copy link
Author

vkryukov commented Dec 2, 2025

Thanks a lot for the feedback! I hope to be able to implement your suggestions soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants