Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve rewards #1731

Merged
merged 12 commits into from
Dec 18, 2024
Merged

improve rewards #1731

merged 12 commits into from
Dec 18, 2024

Conversation

huumn
Copy link
Member

@huumn huumn commented Dec 17, 2024

@ekzyis this just needs a quick sanity check. I backtested it and it looks good.

The goal here is to better reward desirable behavior. This PR:

  1. improve how we determine and score value
  2. remove the fixed number of stackers that get rewarded everyday

1

The main improvement this makes is allowing early zapping contribute independently of zap amount to the score of zapping "value".

Before this change, we scored zapping like zap_size_relative/ln(zap_number). Conceptually, this meant that if you didn't zap early, zapping a lot wouldn't be scored as very valuable. Visually:

Screenshot 2024-12-17 at 11 51 37 AM

After this change, we score zapping like 1/ln(zap_number) + zap_size_relative. Conceptually, this means that zapping a lot contributes to your score independently of zapping early. Visually:

Screenshot 2024-12-17 at 11 50 17 AM

other stuff in value function

  • scale relative zap amount by the quad root rather than logarithmically (conceptually this means larger zaps are rewarded more than before)
  • removed kr from nerfing
  • capture a longer tail of content/zapping (top 50%)
    • this won't matter much except to allow us to record more data - "value" is still weighted by the relative value of the content, so the long tail isn't likely to be rewarded
  • increase tip minimum for consideration
  • increase nerfed value

diff of plpgsql function

 CREATE OR REPLACE FUNCTION user_values(
     min TIMESTAMP(3), max TIMESTAMP(3), ival INTERVAL, date_part TEXT,
-    percentile_cutoff INTEGER DEFAULT 33,
+    percentile_cutoff INTEGER DEFAULT 50,
     each_upvote_portion FLOAT DEFAULT 4.0,
     each_item_portion FLOAT DEFAULT 4.0,
-    handicap_ids INTEGER[] DEFAULT '{616, 6030, 946, 4502}',
-    handicap_zap_mult FLOAT DEFAULT 0.2)
+    handicap_ids INTEGER[] DEFAULT '{616, 6030, 4502, 27}',
+    handicap_zap_mult FLOAT DEFAULT 0.3)
 RETURNS TABLE (
     t TIMESTAMP(3), id INTEGER, proportion FLOAT
 )
@@ -49,23 +49,26 @@ BEGIN
         ),
         -- isolate contiguous upzaps from the same user on the same item so that when we take the log
         -- of the upzaps it accounts for successive zaps and does not disproportionately reward them
+        -- quad root of the total tipped
         upvoters AS (
-            SELECT "userId", upvoter_islands.id, ratio, "parentId", GREATEST(log(sum(tipped) / 1000), 0) as tipped, min(acted_at) as acted_at
+            SELECT "userId", upvoter_islands.id, ratio, "parentId", GREATEST(power(sum(tipped) / 1000, 0.25), 0) as tipped, min(acted_at) as acted_at
             FROM upvoter_islands
             GROUP BY "userId", upvoter_islands.id, ratio, "parentId", island
         ),
         -- the relative contribution of each upvoter to the post/comment
-        -- early multiplier: 10/ln(early_rank + e)
-        -- we also weight by trust in a step wise fashion
+        -- early component: 1/ln(early_rank + e - 1)
+        -- tipped component: how much they tipped relative to the total tipped for the item
+        -- multiplied by the relative rank of the item to the total items
+        -- multiplied by the trust of the user
         upvoter_ratios AS (
-            SELECT "userId", sum(early_multiplier*tipped_ratio*ratio*CASE WHEN users.id = ANY (handicap_ids) THEN handicap_zap_mult ELSE FLOOR(users.trust*3)+handicap_zap_mult END) as upvoter_ratio,
+            SELECT "userId", sum((early_multiplier+tipped_ratio)*ratio*CASE WHEN users.id = ANY (handicap_ids) THEN handicap_zap_mult ELSE users.trust+0.1 END) as upvoter_ratio,
                 "parentId" IS NULL as "isPost", CASE WHEN "parentId" IS NULL THEN 'TIP_POST' ELSE 'TIP_COMMENT' END as type
             FROM (
                 SELECT *,
-                    10.0/LN(ROW_NUMBER() OVER (partition by upvoters.id order by acted_at asc) + EXP(1.0)) AS early_multiplier,
+                    1.0/LN(ROW_NUMBER() OVER (partition by upvoters.id order by acted_at asc) + EXP(1.0) - 1) AS early_multiplier,
                     tipped::float/(sum(tipped) OVER (partition by upvoters.id)) tipped_ratio
                 FROM upvoters
-                WHERE tipped > 0
+                WHERE tipped > 2
             ) u
             JOIN users on "userId" = users.id
             GROUP BY "userId", "parentId" IS NULL

TODO:

  • backtest value changes
  • do (2)
  • test rewards

@huumn huumn marked this pull request as ready for review December 18, 2024 01:47
@huumn huumn requested a review from ekzyis December 18, 2024 02:33
Copy link
Member

@ekzyis ekzyis left a comment

Choose a reason for hiding this comment

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

The charts would have helped more if the axis were labeled 👀 But I got the idea.

Found a bug if we sort stackers by anything other than value.

Unrelated to the changes here but I think in the switch at api/resolvers/user.js:69, we should add a case for value so it's clearer when the proportion column is needed:

diff --git a/api/resolvers/user.js b/api/resolvers/user.js
index ef53de63..cec3fc49 100644
--- a/api/resolvers/user.js
+++ b/api/resolvers/user.js
@@ -66,11 +66,12 @@ export async function topUsers (parent, { cursor, when, by, from, to, limit = LI
     case 'comments': column = 'ncomments'; break
     case 'referrals': column = 'referrals'; break
     case 'stacking': column = 'stacked'; break
+    case 'value':
     default: column = 'proportion'; break
   }

I'll take a closer look at the postgres function now.

api/resolvers/user.js Outdated Show resolved Hide resolved
api/typeDefs/user.js Show resolved Hide resolved
worker/earn.js Show resolved Hide resolved
1.0/LN(ROW_NUMBER() OVER (partition by upvoters.id order by acted_at asc) + EXP(1.0) - 1) AS early_multiplier,
tipped::float/(sum(tipped) OVER (partition by upvoters.id)) tipped_ratio
FROM upvoters
WHERE tipped > 2
Copy link
Member

Choose a reason for hiding this comment

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

why was this changed from > 0 to > 2?

Copy link
Member Author

@huumn huumn Dec 18, 2024

Choose a reason for hiding this comment

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

This enforces a minimum zap to be considered for zapping rewards. Because we use the quad root of the zap amount, this means the minimum zap for consideration is 2 = x^0.25 or x = 16 sats.

It's arbitrary.

I thought about increasing it more.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe I should increase it more.

Copy link
Member Author

@huumn huumn Dec 18, 2024

Choose a reason for hiding this comment

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

An alternative to setting this limit would to weight zap order by the zap amount.

Rather than 1/ln(zap_number) + zap_size_relative, do zap_size_relative*(1/ln(zap_number) + 1).

Conceptually this would mean that by being first, all things being equal, you could double the "value" of your zap. And if your zap is really small relative to other zaps, doubling its "value" wouldn't amount to much.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I should increase it more.

Sounds good, I would rather make it too large than too small

An alternative to setting this limit would to weight zap order by the zap amount.

This would mean that zap amount now becomes more important than zap order, no?

@huumn
Copy link
Member Author

huumn commented Dec 18, 2024

The charts would have helped more if the axis were labeled 👀 But I got the idea.

z in blue is value, x in red is zap order, y in green is zap amount

@huumn huumn merged commit 6d4dfdd into master Dec 18, 2024
6 checks passed
@huumn huumn deleted the improve-values branch December 18, 2024 16:12
@huumn
Copy link
Member Author

huumn commented Dec 18, 2024

It occurred to me too late, but this factor, CASE WHEN users.id = ANY (handicap_ids) THEN handicap_zap_mult ELSE users.trust+0.1 END, should be removed. This represents a stacker's zap trust.

We shouldn't need it because zaps are rewarded relative to an item's zaprank, which effectively means we're weighting a zap's value by the square of a stacker's trust, which makes valuing overly reliant on trust.

When I was backtesting variations, some variations weren't behaving as expected, and this is likely the culprit. This factor can basically lead to very small zaps from highly trusted stackers capturing more rewards than they deserve.

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