7 "github.com/shopspring/decimal"
8 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
12 decimal.MarshalJSONWithoutQuotes = true
15 const MARGIN_POSITION_SECURED_RATIO = 1.0
19 var MINIMAL_BTC_VALUE_TRESHOLD decimal.Decimal = decimal.NewFromFloat(10.0).Pow(decimal.NewFromFloat(-3.0))
21 type ValuePerformance struct {
22 Value decimal.Decimal `json:"value"`
23 Variation decimal.Decimal `json:"variation"`
24 VariationP decimal.Decimal `json:"variationP"`
27 func NewValuePerformance(fromValue, toValue decimal.Decimal) ValuePerformance {
28 variation := toValue.Sub(fromValue)
30 return ValuePerformance{
33 VariationP: variation.Div(fromValue).Mul(decimal.NewFromFloat(100.0)),
37 type PositionType string
39 const POSITION_SHORT PositionType = "short"
40 const POSITION_LONG PositionType = "long"
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"`
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"`
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
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)
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) {
83 currencies[currency] = struct{}{}
86 for currency := range currencies {
87 performances[currency] = NewValuePerformance(from.GetBTCRate(currency), to.GetBTCRate(currency))
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)
98 var deltaBTC decimal.Decimal
100 currencies := make(map[string]struct{})
101 for currency := range to.Balances {
102 currencies[currency] = struct{}{}
104 for currency := range from.Balances {
105 currencies[currency] = struct{}{}
108 for currency := range currencies {
109 balanceFrom := from.Balances[currency]
110 balanceTo := to.Balances[currency]
112 delta := balanceTo.Total.Sub(balanceFrom.Total)
113 if !delta.Equals(decimal.Zero) {
114 deltaBTC = deltaBTC.Add(delta.Mul(to.Tickers.GetBTCRate(currency)).Neg())
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)
128 diff, err := UserMovements(from, to)
130 return decimal.Zero, err
133 return to.Tickers.Total.Sub(from.Tickers.Total).Add(diff), nil
136 func ComputeWeights(report db.Report) map[string]decimal.Decimal {
137 weights := make(map[string]decimal.Decimal)
139 for currency := range report.Balances {
141 if report.Tickers.Balances[currency].Abs().LessThan(MINIMAL_BTC_VALUE_TRESHOLD) {
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))).
149 Div(report.Tickers.Total).
150 Mul(decimal.NewFromFloat(100))
156 func GetWeekPortfolio(marketConfig db.MarketConfig) (Portfolio, error) {
157 portfolio := Portfolio{
158 Balances: make(map[string]PortfolioBalance),
161 report, err := db.GetLastPortfolioBegin(marketConfig)
163 return portfolio, err
167 return portfolio, &Error{NotFound, "no report", fmt.Errorf("no reports for marketConfigId '%v'", marketConfig.Id)}
170 liveReport, err := db.GetLatestReport(marketConfig)
172 return portfolio, err
175 weights := ComputeWeights(*report)
176 currenciesPerformances := GetCurrenciesPerformance(report.Tickers, liveReport.Tickers)
178 portfolio.PeriodStart = report.Date.Truncate(time.Second).UTC()
179 portfolio.PeriodEnd = liveReport.Date.Truncate(time.Second).UTC()
181 for currency := range liveReport.Balances {
182 balance := liveReport.Balances[currency]
183 btcTicker := liveReport.Tickers.Balances[currency]
185 if btcTicker.Abs().LessThan(MINIMAL_BTC_VALUE_TRESHOLD) {
189 var positionType PositionType
190 var perfMul decimal.Decimal
192 if balance.Total.LessThan(decimal.Zero) {
193 positionType = POSITION_SHORT
194 perfMul = decimal.NewFromFloat(-1.0)
196 positionType = POSITION_LONG
197 perfMul = decimal.NewFromFloat(1.0)
200 portfolio.Balances[currency] = PortfolioBalance{
201 PositionType: positionType,
202 Quantity: balance.Total,
204 QuantityLocked: balance.MarginInPosition.Mul(decimal.NewFromFloat(1.0 + MARGIN_POSITION_SECURED_RATIO)),
205 Weight: weights[currency],
206 PositionPerformanceP: currenciesPerformances[currency].VariationP.Mul(perfMul),
210 portfolio.Value = liveReport.Tickers.Total
211 plusValue, err := ComputePlusValue(*report, liveReport)
213 return portfolio, err
216 portfolio.Performance = NewValuePerformance(liveReport.Tickers.Total.Sub(plusValue), liveReport.Tickers.Total)
218 return portfolio, nil