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