reputation.go raw
1 package market
2
3 import (
4 "fmt"
5 "sync"
6 "time"
7
8 "git.mleku.dev/mleku/dendrite/pkg/axiom"
9 )
10
11 // Reputation tracks the accuracy of information signals over time.
12 // Authors who signal dislocations that subsequently resolve profitably
13 // accumulate reputation. Authors whose signals prove false lose it.
14 // This is the organism's second infrastructure service: weighting
15 // information by demonstrated accuracy rather than identity or volume.
16 type Reputation struct {
17 mu sync.RWMutex
18 authors map[string]*AuthorRecord
19 }
20
21 // AuthorRecord tracks one author's signal accuracy.
22 type AuthorRecord struct {
23 Pubkey string
24 Signals int // total signals recorded
25 Correct int // signals where predicted direction matched outcome
26 Incorrect int // signals where prediction was wrong
27 Accuracy float64 // correct / signals
28 Score float64 // weighted reputation score [0, 1]
29 LastSignal time.Time
30 }
31
32 // Prediction is a recorded signal with its outcome.
33 type Prediction struct {
34 Pubkey string
35 Asset string
36 Direction int // +1 bullish, -1 bearish
37 Time time.Time
38 }
39
40 // Outcome records whether a prediction was correct.
41 type Outcome struct {
42 Prediction Prediction
43 Correct bool
44 PriceMove float64 // actual price change percentage
45 ResolvedAt time.Time
46 }
47
48 // NewReputation creates an empty reputation tracker.
49 func NewReputation() *Reputation {
50 return &Reputation{
51 authors: make(map[string]*AuthorRecord),
52 }
53 }
54
55 // RecordSignal registers a new prediction from an author.
56 func (r *Reputation) RecordSignal(pred Prediction) {
57 r.mu.Lock()
58 defer r.mu.Unlock()
59
60 rec, ok := r.authors[pred.Pubkey]
61 if !ok {
62 rec = &AuthorRecord{Pubkey: pred.Pubkey}
63 r.authors[pred.Pubkey] = rec
64 }
65 rec.Signals++
66 rec.LastSignal = pred.Time
67 }
68
69 // RecordOutcome updates an author's record with the result of a prediction.
70 func (r *Reputation) RecordOutcome(outcome Outcome) {
71 r.mu.Lock()
72 defer r.mu.Unlock()
73
74 rec, ok := r.authors[outcome.Prediction.Pubkey]
75 if !ok {
76 return // no record for this author
77 }
78
79 if outcome.Correct {
80 rec.Correct++
81 } else {
82 rec.Incorrect++
83 }
84
85 if rec.Signals > 0 {
86 rec.Accuracy = float64(rec.Correct) / float64(rec.Signals)
87 }
88
89 // Score is accuracy weighted by signal count, with decay for few signals.
90 // An author with 1 correct signal out of 1 is not as credible as
91 // an author with 50 correct out of 60.
92 minSamples := 10.0
93 confidence := float64(rec.Signals) / (float64(rec.Signals) + minSamples)
94 rec.Score = rec.Accuracy * confidence
95 }
96
97 // Author returns the reputation record for a pubkey, or nil.
98 func (r *Reputation) Author(pubkey string) *AuthorRecord {
99 r.mu.RLock()
100 defer r.mu.RUnlock()
101 return r.authors[pubkey]
102 }
103
104 // TopAuthors returns the N authors with highest reputation scores.
105 func (r *Reputation) TopAuthors(n int) []*AuthorRecord {
106 r.mu.RLock()
107 defer r.mu.RUnlock()
108
109 all := make([]*AuthorRecord, 0, len(r.authors))
110 for _, rec := range r.authors {
111 all = append(all, rec)
112 }
113
114 // Simple selection sort for small N.
115 for i := 0; i < len(all) && i < n; i++ {
116 best := i
117 for j := i + 1; j < len(all); j++ {
118 if all[j].Score > all[best].Score {
119 best = j
120 }
121 }
122 all[i], all[best] = all[best], all[i]
123 }
124
125 if n > len(all) {
126 n = len(all)
127 }
128 return all[:n]
129 }
130
131 // AuthorCount returns the number of tracked authors.
132 func (r *Reputation) AuthorCount() int {
133 r.mu.RLock()
134 defer r.mu.RUnlock()
135 return len(r.authors)
136 }
137
138 // AuthorRecordToElements decomposes an author's reputation into lattice elements.
139 func AuthorRecordToElements(rec *AuthorRecord) []axiom.Element {
140 return []axiom.Element{
141 element{"pubkey", rec.Pubkey},
142 element{"signal-count", rec.Signals},
143 element{"signal-correct", rec.Correct},
144 element{"signal-incorrect", rec.Incorrect},
145 element{"accuracy", rec.Accuracy},
146 element{"reputation-score", rec.Score},
147 element{"timestamp", fmt.Sprintf("%d", rec.LastSignal.Unix())},
148 }
149 }
150