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