]>
Commit | Line | Data |
---|---|---|
24e47979 | 1 | package api |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "time" | |
6 | ||
7 | "github.com/shopspring/decimal" | |
8 | "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" | |
9 | ) | |
10 | ||
11 | func init() { | |
12 | decimal.MarshalJSONWithoutQuotes = true | |
13 | } | |
14 | ||
15 | const MARGIN_POSITION_SECURED_RATIO = 1.0 | |
16 | ||
17 | const BTC_DIGITS = 3 | |
18 | ||
19 | var MINIMAL_BTC_VALUE_TRESHOLD decimal.Decimal = decimal.NewFromFloat(10.0).Pow(decimal.NewFromFloat(-3.0)) | |
20 | ||
21 | type ValuePerformance struct { | |
22 | Value decimal.Decimal `json:"value"` | |
23 | Variation decimal.Decimal `json:"variation"` | |
24 | VariationP decimal.Decimal `json:"variationP"` | |
25 | } | |
26 | ||
27 | func NewValuePerformance(fromValue, toValue decimal.Decimal) ValuePerformance { | |
28 | variation := toValue.Sub(fromValue) | |
29 | ||
30 | return ValuePerformance{ | |
31 | Value: toValue, | |
32 | Variation: variation, | |
33 | VariationP: variation.Div(fromValue).Mul(decimal.NewFromFloat(100.0)), | |
34 | } | |
35 | } | |
36 | ||
37 | type PositionType string | |
38 | ||
39 | const POSITION_SHORT PositionType = "short" | |
40 | const POSITION_LONG PositionType = "long" | |
41 | ||
42 | type PortfolioBalance struct { | |
43 | PositionType PositionType `json:"positionType"` | |
44 | Quantity decimal.Decimal `json:"quantity"` | |
45 | QuantityLocked decimal.Decimal `json:"quantityLocked"` | |
46 | BTCValue decimal.Decimal `json:"BTCValue"` | |
47 | PositionPerformanceP decimal.Decimal `json:"positionPerformanceP"` | |
48 | Weight decimal.Decimal `json:"weight"` | |
49 | } | |
50 | ||
51 | type Portfolio struct { | |
52 | PeriodStart time.Time `json:"periodStart"` | |
53 | PeriodEnd time.Time `json:"periodEnd"` | |
54 | Balances map[string]PortfolioBalance `json:"balances"` | |
55 | Value decimal.Decimal `json:"value"` | |
56 | Performance ValuePerformance `json:"performance"` | |
57 | } | |
58 | ||
59 | func (p Portfolio) Round() Portfolio { | |
60 | p.Value = p.Value.Round(BTC_DIGITS) | |
61 | for currency := range p.Balances { | |
62 | balance := p.Balances[currency] | |
63 | balance.Quantity = balance.Quantity.Round(2) | |
64 | balance.BTCValue = balance.BTCValue.Round(BTC_DIGITS) | |
65 | balance.Weight = balance.Weight.Round(1) | |
66 | balance.PositionPerformanceP = balance.PositionPerformanceP.Round(1) | |
67 | p.Balances[currency] = balance | |
68 | } | |
69 | ||
70 | p.Performance.VariationP = p.Performance.VariationP.Round(1) | |
71 | p.Performance.Variation = p.Performance.Variation.Round(BTC_DIGITS) | |
72 | p.Performance.Value = p.Performance.Value.Round(BTC_DIGITS) | |
73 | return p | |
74 | } | |
75 | ||
76 | func GetCurrenciesPerformance(from, to db.ReportTickers) map[string]ValuePerformance { | |
77 | performances := make(map[string]ValuePerformance) | |
78 | currencies := make(map[string]struct{}) | |
79 | for currency := range to.Balances { | |
80 | if to.Balances[currency].Abs().LessThan(MINIMAL_BTC_VALUE_TRESHOLD) { | |
81 | continue | |
82 | } | |
83 | currencies[currency] = struct{}{} | |
84 | } | |
85 | ||
86 | for currency := range currencies { | |
87 | performances[currency] = NewValuePerformance(from.GetBTCRate(currency), to.GetBTCRate(currency)) | |
88 | } | |
89 | ||
90 | return performances | |
91 | } | |
92 | ||
93 | func UserMovements(from db.Report, to db.Report) (decimal.Decimal, error) { | |
94 | if from.Tag != db.BUY_END || (to.Tag != db.SELL_BEGIN && to.Tag != db.INTERMADIATE_STATE) { | |
95 | return decimal.Zero, fmt.Errorf("cannot compare reports: '%s' -> '%s'", from.Tag, to.Tag) | |
96 | } | |
97 | ||
98 | var deltaBTC decimal.Decimal | |
99 | ||
100 | currencies := make(map[string]struct{}) | |
101 | for currency := range to.Balances { | |
102 | currencies[currency] = struct{}{} | |
103 | } | |
104 | for currency := range from.Balances { | |
105 | currencies[currency] = struct{}{} | |
106 | } | |
107 | ||
108 | for currency := range currencies { | |
109 | balanceFrom := from.Balances[currency] | |
110 | balanceTo := to.Balances[currency] | |
111 | ||
112 | delta := balanceTo.Total.Sub(balanceFrom.Total) | |
113 | if !delta.Equals(decimal.Zero) { | |
114 | deltaBTC = deltaBTC.Add(delta.Mul(to.Tickers.GetBTCRate(currency)).Neg()) | |
115 | } | |
116 | ||
117 | } | |
118 | ||
119 | return deltaBTC, nil | |
120 | } | |
121 | ||
122 | // Computes plus-value, ignoring positions took by users. | |
123 | func ComputePlusValue(from db.Report, to db.Report) (decimal.Decimal, error) { | |
124 | if from.Tag != db.BUY_END || (to.Tag != db.SELL_BEGIN && to.Tag != db.INTERMADIATE_STATE) { | |
125 | return decimal.Zero, fmt.Errorf("cannot compare reports: '%s' -> '%s'", from.Tag, to.Tag) | |
126 | } | |
127 | ||
128 | diff, err := UserMovements(from, to) | |
129 | if err != nil { | |
130 | return decimal.Zero, err | |
131 | } | |
132 | ||
133 | return to.Tickers.Total.Sub(from.Tickers.Total).Add(diff), nil | |
134 | } | |
135 | ||
136 | func ComputeWeights(report db.Report) map[string]decimal.Decimal { | |
137 | weights := make(map[string]decimal.Decimal) | |
138 | ||
139 | for currency := range report.Balances { | |
140 | ||
141 | if report.Tickers.Balances[currency].Abs().LessThan(MINIMAL_BTC_VALUE_TRESHOLD) { | |
142 | continue | |
143 | } | |
144 | ||
145 | quantityBlocked := report.Balances[currency].MarginInPosition.Mul(decimal.NewFromFloat(1.0 + MARGIN_POSITION_SECURED_RATIO)) | |
146 | weights[currency] = report.Tickers.Balances[currency]. | |
147 | Sub(quantityBlocked.Mul(report.Tickers.GetBTCRate(currency))). | |
148 | Abs(). | |
149 | Div(report.Tickers.Total). | |
150 | Mul(decimal.NewFromFloat(100)) | |
151 | } | |
152 | ||
153 | return weights | |
154 | } | |
155 | ||
156 | func GetWeekPortfolio(marketConfig db.MarketConfig) (Portfolio, error) { | |
157 | portfolio := Portfolio{ | |
158 | Balances: make(map[string]PortfolioBalance), | |
159 | } | |
160 | ||
161 | report, err := db.GetLastPortfolioBegin(marketConfig) | |
162 | if err != nil { | |
163 | return portfolio, err | |
164 | } | |
165 | ||
166 | if report == nil { | |
167 | return portfolio, &Error{NotFound, "no report", fmt.Errorf("no reports for marketConfigId '%v'", marketConfig.Id)} | |
168 | } | |
169 | ||
170 | liveReport, err := db.GetLatestReport(marketConfig) | |
171 | if err != nil { | |
172 | return portfolio, err | |
173 | } | |
174 | ||
175 | weights := ComputeWeights(*report) | |
176 | currenciesPerformances := GetCurrenciesPerformance(report.Tickers, liveReport.Tickers) | |
177 | ||
178 | portfolio.PeriodStart = report.Date.Truncate(time.Second).UTC() | |
179 | portfolio.PeriodEnd = liveReport.Date.Truncate(time.Second).UTC() | |
180 | ||
181 | for currency := range liveReport.Balances { | |
182 | balance := liveReport.Balances[currency] | |
183 | btcTicker := liveReport.Tickers.Balances[currency] | |
184 | ||
185 | if btcTicker.Abs().LessThan(MINIMAL_BTC_VALUE_TRESHOLD) { | |
186 | continue | |
187 | } | |
188 | ||
189 | var positionType PositionType | |
190 | var perfMul decimal.Decimal | |
191 | ||
192 | if balance.Total.LessThan(decimal.Zero) { | |
193 | positionType = POSITION_SHORT | |
194 | perfMul = decimal.NewFromFloat(-1.0) | |
195 | } else { | |
196 | positionType = POSITION_LONG | |
197 | perfMul = decimal.NewFromFloat(1.0) | |
198 | } | |
199 | ||
200 | portfolio.Balances[currency] = PortfolioBalance{ | |
201 | PositionType: positionType, | |
202 | Quantity: balance.Total, | |
203 | BTCValue: btcTicker, | |
204 | QuantityLocked: balance.MarginInPosition.Mul(decimal.NewFromFloat(1.0 + MARGIN_POSITION_SECURED_RATIO)), | |
205 | Weight: weights[currency], | |
206 | PositionPerformanceP: currenciesPerformances[currency].VariationP.Mul(perfMul), | |
207 | } | |
208 | } | |
209 | ||
210 | portfolio.Value = liveReport.Tickers.Total | |
211 | plusValue, err := ComputePlusValue(*report, liveReport) | |
212 | if err != nil { | |
213 | return portfolio, err | |
214 | } | |
215 | ||
216 | portfolio.Performance = NewValuePerformance(liveReport.Tickers.Total.Sub(plusValue), liveReport.Tickers.Total) | |
217 | ||
218 | return portfolio, nil | |
219 | } |