Skip to content

Commit 6fd52cc

Browse files
committed
Number of lots + hovered line + score calculation update
1 parent 8b955d4 commit 6fd52cc

File tree

10 files changed

+138
-37
lines changed

10 files changed

+138
-37
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ _score value is not a ground truth but we try to make it as more objective as po
5151
### How to use
5252
* Link `🌐` allows to open bonds page in Tinkoff investment (T) and MOEX (M)
5353
* Link `🔍` allows to inspect known coupons of corresponding bond
54+
* Column `👝` shows number of lots in your portfolio (sum by all your accounts)
5455
* Symbol `➥` marks next coupon, symbol `⭿` mark coupon after offer is committed
5556
* Credit values are calculated with consideration to the inflation with the equation:
5657
let `S` some sum, let `D` number of a days and `I` is inflation in percents then

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>ru.shemplo</groupId>
66
<artifactId>tinkoff-bond-scanner</artifactId>
7-
<version>1.0</version>
7+
<version>1.1</version>
88

99
<properties>
1010
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

src/main/java/ru/shemplo/tbs/Bond.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class Bond implements Serializable {
2727

2828
private String name, code;
2929
private Currency currency;
30+
private int lots;
3031

3132
private LocalDate start, end, nextCoupon;
3233
private long couponsPerYear;
@@ -40,8 +41,9 @@ public class Bond implements Serializable {
4041

4142
private String primaryBoard;
4243

43-
public Bond (MarketInstrument instrument) {
44+
public Bond (MarketInstrument instrument, int lots) {
4445
currency = instrument.getCurrency ();
46+
this.lots = lots;
4547

4648
final var MOEX_DESCRIPION_URL = MOEXRequests.makeBondDescriptionURLForMOEX (instrument.getTicker ());
4749
final var MOEX_COUPONS_URL = MOEXRequests.makeBondCouponsURLForMOEX (instrument.getTicker ());
@@ -54,7 +56,7 @@ public Bond (MarketInstrument instrument) {
5456

5557
couponsPerYear = description.getBondCouponsPerYear ().orElse (1);
5658
nextCoupon = description.getBondNextCouponDate ().orElse (null);
57-
nominalValue = description.getBondNominalValue ().orElse (0.0);
59+
nominalValue = description.getBondNominalValue ().orElse (1.0);
5860
percentage = description.getBondPercentage ().orElse (0.0);
5961
emitterId = description.getBondEmitterID ().orElse (-1L);
6062
start = description.getBondStartDate ().orElse (null);
@@ -134,7 +136,8 @@ public void updateScore (ITBSProfile profile) {
134136

135137
final var priceBalance = profile.getSafeMaxPrice (lastPrice) - lastPrice;
136138
final var monthsBalance = months - profile.getSafeMinMonths ();
137-
score = pureCredit + monthsBalance * 1.13 + priceBalance * 1.35;
139+
score = pureCredit + monthsBalance * 1.13 + priceBalance * 1.35 - lots * 0.25 + couponsPerYear * 0.25 + percentage * 1.4 - 100.0;
140+
score *= nominalValue != 0.0 ? 1000.0 / nominalValue : 1.0; // align to 1k nominal
138141
}
139142

140143
}

src/main/java/ru/shemplo/tbs/RunTinkoffBondScanner.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
import java.util.Date;
1313
import java.util.List;
1414
import java.util.Locale;
15+
import java.util.Map;
1516
import java.util.Scanner;
1617
import java.util.stream.Collectors;
1718

1819
import javafx.application.Application;
1920
import lombok.extern.slf4j.Slf4j;
2021
import ru.shemplo.tbs.gfx.TBSUIApplication;
2122
import ru.tinkoff.invest.openapi.OpenApi;
23+
import ru.tinkoff.invest.openapi.model.rest.InstrumentType;
24+
import ru.tinkoff.invest.openapi.model.rest.PortfolioPosition;
2225
import ru.tinkoff.invest.openapi.model.rest.SandboxRegisterRequest;
2326
import ru.tinkoff.invest.openapi.okhttp.OkHttpOpenApi;
2427

@@ -82,15 +85,34 @@ private static void openConnection (ITBSProfile profile, String token) {
8285
}
8386

8487
private static void searchForBonds (ITBSProfile profile, OpenApi client) {
88+
final var ticker2lots = searchForPortfolio (profile, client);
89+
//searchForFavorites (profile, client);
90+
8591
log.info ("Loading data abount bonds from Tinkoff and MOEX...");
8692
final var bonds = client.getMarketContext ().getMarketBonds ().join ().getInstruments ().stream ()
87-
. filter (instrument -> profile.getCurrencies ().contains (instrument.getCurrency ()))
88-
. parallel ()
89-
. map (Bond::new).filter (profile::testBond).limit (profile.getMaxResults ())
93+
. filter (instrument -> profile.getCurrencies ().contains (instrument.getCurrency ())).parallel ()
94+
. map (ins -> {
95+
final var lots = ticker2lots.getOrDefault (ins.getTicker (), 0);
96+
return new Bond (ins, lots);
97+
})
98+
. filter (profile::testBond).limit (profile.getMaxResults ())
9099
. collect (Collectors.toList ());
91100
analizeBonds (profile, bonds);
92101
}
93102

103+
private static Map <String, Integer> searchForPortfolio (ITBSProfile profile, OpenApi client) {
104+
return client.getUserContext ().getAccounts ().join ().getAccounts ().parallelStream ().flatMap (acc -> {
105+
return client.getPortfolioContext ().getPortfolio (acc.getBrokerAccountId ()).join ().getPositions ().stream ();
106+
}).filter (pos -> pos.getInstrumentType () == InstrumentType.BOND).collect (Collectors.toMap (
107+
PortfolioPosition::getTicker, PortfolioPosition::getLots, Integer::sum
108+
));
109+
}
110+
111+
@SuppressWarnings ("unused") // Not supported by Tinkoff Open API yet
112+
private static void searchForFavorites (ITBSProfile profile, OpenApi client) {
113+
114+
}
115+
94116
private static void analizeBonds (ITBSProfile profile, List <Bond> bonds) {
95117
log.info ("Analizing loaded bonds (total: {})...", bonds.size ());
96118
bonds.forEach (bond -> bond.updateScore (profile));
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package ru.shemplo.tbs;
2+
3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.function.Consumer;
6+
import java.util.function.Function;
7+
import java.util.stream.Collectors;
8+
9+
public class TBSUtils {
10+
11+
public static <F, S> List <S> mapToList (Collection <F> values, Function <F, S> converter) {
12+
return values.stream ().map (converter).collect (Collectors.toList ());
13+
}
14+
15+
public static <F> F aOrB (F a, F b) {
16+
return a == null ? b : a;
17+
}
18+
19+
public static <F, S> S mapIfNN (F value, Function <F, S> converter, S defaultValue) {
20+
return aOrB (value == null ? null : converter.apply (value), defaultValue);
21+
}
22+
23+
public static <F> void doIfNN (F value, Consumer <F> action) {
24+
if (value != null) { action.accept (value); }
25+
}
26+
27+
}

src/main/java/ru/shemplo/tbs/gfx/TBSExploreTableCell.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import javafx.scene.text.Text;
88
import ru.shemplo.tbs.Bond;
99

10-
public class TBSExploreTableCell extends TBSTableCell <Bond, Void> {
10+
public class TBSExploreTableCell extends TBSTableCell <Bond, Bond> {
1111

1212
private boolean openInTinkoff;
1313

@@ -19,21 +19,22 @@ public TBSExploreTableCell (boolean openInTinkoff) {
1919
}
2020

2121
@Override
22-
protected void updateItem (Bond item, boolean empty) {
22+
protected void updateItem (TBSMetaWrapper <Bond> item, boolean empty) {
2323
super.updateItem (item, empty);
2424
setText (null);
2525

2626
if (item != null) {
2727
final var link = new Text ("🌐");
2828
link.setOnMouseClicked (me -> {
2929
if (me.getButton () == MouseButton.PRIMARY) {
30+
final var code = item.getObject ().getCode ();
3031
if (openInTinkoff) {
3132
TBSUIApplication.getInstance ().openLinkInBrowser (String.format (
32-
"https://www.tinkoff.ru/invest/bonds/%s/", item.getCode ()
33+
"https://www.tinkoff.ru/invest/bonds/%s/", code
3334
));
3435
} else {
3536
TBSUIApplication.getInstance ().openLinkInBrowser (String.format (
36-
"https://www.moex.com/ru/issue.aspx?code=%s&utm_source=www.moex.com", item.getCode ()
37+
"https://www.moex.com/ru/issue.aspx?code=%s&utm_source=www.moex.com", code
3738
));
3839
}
3940
}

src/main/java/ru/shemplo/tbs/gfx/TBSInspectTableCell.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javafx.stage.Window;
2828
import ru.shemplo.tbs.Bond;
2929
import ru.shemplo.tbs.Coupon;
30+
import ru.shemplo.tbs.TBSUtils;
3031

3132
public class TBSInspectTableCell extends TBSTableCell <Bond, Void> {
3233

@@ -38,7 +39,7 @@ public TBSInspectTableCell () {
3839
}
3940

4041
@Override
41-
protected void updateItem (Bond item, boolean empty) {
42+
protected void updateItem (TBSMetaWrapper <Bond> item, boolean empty) {
4243
super.updateItem (item, empty);
4344
setText (null);
4445

@@ -47,7 +48,7 @@ protected void updateItem (Bond item, boolean empty) {
4748
link.setOnMouseClicked (me -> {
4849
if (me.getButton () == MouseButton.PRIMARY) {
4950
final var scene = ((Node) me.getSource ()).getScene ();
50-
showCouponsWindow (scene.getWindow (), item);
51+
showCouponsWindow (scene.getWindow (), item.getObject ());
5152
}
5253
});
5354
link.setCursor (Cursor.HAND);
@@ -72,17 +73,19 @@ private void showCouponsWindow (Window window, Bond bond) {
7273
final var table = initializeTable (bond);
7374
root.getChildren ().add (table);
7475

75-
table.setItems (FXCollections.observableArrayList (bond.getCoupons ()));
76+
table.setItems (FXCollections.observableArrayList (TBSUtils.mapToList (
77+
bond.getCoupons (), TBSMetaWrapper::new
78+
)));
7679
}
7780

78-
private TableView <Coupon> initializeTable (Bond bond) {
79-
final var table = new TableView <Coupon> ();
81+
private TableView <TBSMetaWrapper <Coupon>> initializeTable (Bond bond) {
82+
final var table = new TableView <TBSMetaWrapper <Coupon>> ();
8083
table.setBackground (new Background (new BackgroundFill (Color.LIGHTGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
8184
VBox.setVgrow (table, Priority.ALWAYS);
8285
table.setSelectionModel (null);
8386
table.setBorder (Border.EMPTY);
8487

85-
final var symbolColumn = makeTBSTableColumn ("", Coupon::getSymbol, false, false, Pos.BASELINE_CENTER, 40.0);
88+
final var symbolColumn = makeTBSTableColumn ("", Coupon::getSymbol, false, false, Pos.BASELINE_CENTER, 50.0);
8689
table.getColumns ().add (symbolColumn);
8790

8891
final var dateColumn = makeTBSTableColumn ("Date", Coupon::getDate, false, false, 100.0);
@@ -103,17 +106,17 @@ private TableView <Coupon> initializeTable (Bond bond) {
103106
return table;
104107
}
105108

106-
public static <T> TableColumn <Coupon, Coupon> makeTBSTableColumn (
109+
public static <T> TableColumn <TBSMetaWrapper <Coupon>, TBSMetaWrapper <Coupon>> makeTBSTableColumn (
107110
String name, Function <Coupon, T> converter, boolean sortable, boolean colorized, double minWidth
108111
) {
109112
return makeTBSTableColumn (name, converter, sortable, colorized, Pos.BASELINE_LEFT, minWidth);
110113
}
111114

112-
public static <T> TableColumn <Coupon, Coupon> makeTBSTableColumn (
115+
public static <T> TableColumn <TBSMetaWrapper <Coupon>, TBSMetaWrapper <Coupon>> makeTBSTableColumn (
113116
String name, Function <Coupon, T> converter, boolean sortable, boolean colorized,
114117
Pos textAlignment, double minWidth
115118
) {
116-
final var column = new TableColumn <Coupon, Coupon> (name);
119+
final var column = new TableColumn <TBSMetaWrapper <Coupon>, TBSMetaWrapper <Coupon>> (name);
117120
column.setCellFactory (__ -> new TBSTableCell <> (converter, colorized, textAlignment));
118121
column.setCellValueFactory (cell -> {
119122
return new SimpleObjectProperty <> (cell.getValue ());
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ru.shemplo.tbs.gfx;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.Setter;
6+
7+
@Getter @Setter
8+
@RequiredArgsConstructor
9+
public class TBSMetaWrapper <T> {
10+
11+
private final T object;
12+
13+
private boolean hovered;
14+
15+
}

src/main/java/ru/shemplo/tbs/gfx/TBSTableCell.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,49 @@
44

55
import javafx.geometry.Pos;
66
import javafx.scene.control.TableCell;
7+
import javafx.scene.layout.Background;
8+
import javafx.scene.layout.BackgroundFill;
79
import javafx.scene.paint.Color;
810
import javafx.scene.text.Font;
911
import javafx.scene.text.FontWeight;
10-
import lombok.RequiredArgsConstructor;
12+
import ru.shemplo.tbs.TBSUtils;
1113

12-
@RequiredArgsConstructor
13-
public class TBSTableCell <F, S> extends TableCell <F, F> {
14+
public class TBSTableCell <F, S> extends TableCell <TBSMetaWrapper <F>, TBSMetaWrapper <F>> {
1415

1516
private final Function <F, S> converter;
1617
private final boolean colorizeNumbers;
1718

19+
private static final Background HOVER_BG = new Background (new BackgroundFill (Color.rgb (220, 240, 245, 1.0), null, null));
1820
private static final Font COLOR_FONT = Font.font ("Consolas", FontWeight.NORMAL, 12.0);
1921

20-
public TBSTableCell (Function <F, S> converter, boolean colorizeNumbers, Pos textAlignment) {
22+
private Background defaultBackground;
23+
24+
public TBSTableCell (Function <F, S> converter, boolean colorizeNumbers) {
2125
this.converter = converter; this.colorizeNumbers = colorizeNumbers;
26+
27+
hoverProperty ().addListener ((__, ___, hovered) -> {
28+
if (getItem () == null) { return; }
29+
30+
if (hovered) {
31+
defaultBackground = getTableRow ().getBackground ();
32+
getTableRow ().setBackground (HOVER_BG);
33+
} else {
34+
getTableRow ().setBackground (defaultBackground);
35+
}
36+
});
37+
}
38+
39+
public TBSTableCell (Function <F, S> converter, boolean colorizeNumbers, Pos textAlignment) {
40+
this (converter, colorizeNumbers);
2241
setAlignment (textAlignment);
2342
}
2443

2544
@Override
26-
protected void updateItem (F item, boolean empty) {
45+
protected void updateItem (TBSMetaWrapper <F> item, boolean empty) {
46+
if (TBSUtils.mapIfNN (item, TBSMetaWrapper::isHovered, false)) {
47+
setBackground (new Background (new BackgroundFill (Color.YELLOW, null, null)));
48+
}
49+
2750
if (item == getItem ()) { return; }
2851
super.updateItem (item, empty);
2952

@@ -34,12 +57,12 @@ protected void updateItem (F item, boolean empty) {
3457
return;
3558
}
3659

37-
final var value = converter.apply (item);
60+
final var value = converter.apply (item.getObject ());
3861
if (value instanceof Number n) {
3962
if (colorizeNumbers) {
4063
setFont (COLOR_FONT);
4164

42-
if (n.doubleValue () > 1e-6) {
65+
if (n.doubleValue () > 1e-6) {
4366
setTextFill (Color.GREEN);
4467
} else if (n.doubleValue () < -1e-6) {
4568
setTextFill (Color.RED);

0 commit comments

Comments
 (0)