Skip to content

Commit 2a3403c

Browse files
Tar63tarik
authored andcommitted
Add bist trade calendar and implement hijri dates.
- Add 'islamicDateRange()' as a new HolidayType for Islamic holidays. - Add new imports for HolidaysType.java. - Change TradeCalendar.java to return multiple holidays per year. - Add new holiday names to holiday-names.properties. - Add new calendar name to messages.properties. - Add translations for the new names from properties files. - Add new Calendar tests as a dedicated BorsaIstanbulTradeCalendarTest.java, following the TelAvivStockExchangeTradeCalendarTest pattern.
1 parent 90ddbf7 commit 2a3403c

45 files changed

Lines changed: 631 additions & 50 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package name.abuchen.portfolio.util;
2+
3+
import static org.hamcrest.CoreMatchers.is;
4+
import static org.hamcrest.MatcherAssert.assertThat;
5+
6+
import java.time.LocalDate;
7+
8+
import org.junit.Test;
9+
10+
/**
11+
* See https://www.borsaistanbul.com/en/official-holidays
12+
*/
13+
@SuppressWarnings("nls")
14+
public class BorsaIstanbulTradeCalendarTest
15+
{
16+
@Test
17+
public void testRamadanFeast()
18+
{
19+
var calendar = TradeCalendarManager.getInstance("bist");
20+
21+
// Regular years
22+
assertThat(calendar.isHoliday(LocalDate.parse("2026-03-19")), is(false)); // Thursday before feast
23+
assertThat(calendar.isHoliday(LocalDate.parse("2026-03-20")), is(true));
24+
assertThat(calendar.isHoliday(LocalDate.parse("2026-03-22")), is(true));
25+
assertThat(calendar.isHoliday(LocalDate.parse("2026-03-23")), is(false));
26+
27+
// 2016: feast shifted one day earlier by BIST (days 6–8 → 5–7)
28+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-04")), is(false));
29+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-05")), is(true));
30+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-06")), is(true));
31+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-07")), is(true));
32+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-08")), is(false));
33+
}
34+
35+
@Test
36+
public void testSacrificeFeast()
37+
{
38+
var calendar = TradeCalendarManager.getInstance("bist");
39+
40+
// Regular years (2023: days 1–3 on weekdays; day 4 = Saturday)
41+
assertThat(calendar.isHoliday(LocalDate.parse("2023-06-28")), is(true));
42+
assertThat(calendar.isHoliday(LocalDate.parse("2023-06-29")), is(true));
43+
assertThat(calendar.isHoliday(LocalDate.parse("2023-06-30")), is(true));
44+
assertThat(calendar.isHoliday(LocalDate.parse("2025-06-06")), is(true));
45+
assertThat(calendar.isHoliday(LocalDate.parse("2025-06-09")), is(true));
46+
assertThat(calendar.isHoliday(LocalDate.parse("2025-06-10")), is(false));
47+
48+
// 2012: feast shifted one day earlier by BIST (days 26–29 → 25–28)
49+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-24")), is(false));
50+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-25")), is(true));
51+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-26")), is(true));
52+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-28")), is(true)); // also Republic Day
53+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-30")), is(false));
54+
55+
// 2015: feast shifted one day later by BIST (days 23–26 → 24–27)
56+
assertThat(calendar.isHoliday(LocalDate.parse("2015-09-23")), is(false));
57+
assertThat(calendar.isHoliday(LocalDate.parse("2015-09-24")), is(true));
58+
assertThat(calendar.isHoliday(LocalDate.parse("2015-09-25")), is(true));
59+
assertThat(calendar.isHoliday(LocalDate.parse("2015-09-27")), is(true));
60+
assertThat(calendar.isHoliday(LocalDate.parse("2015-09-28")), is(false));
61+
62+
// 2016: feast shifted one day later by BIST (days 11–14 → 12–15)
63+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-09")), is(false)); // Friday before feast
64+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-12")), is(true));
65+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-13")), is(true));
66+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-14")), is(true));
67+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-15")), is(true));
68+
assertThat(calendar.isHoliday(LocalDate.parse("2016-09-16")), is(false));
69+
}
70+
71+
@Test
72+
public void testStaticHolidays()
73+
{
74+
var calendar = TradeCalendarManager.getInstance("bist");
75+
76+
// New Year
77+
assertThat(calendar.isHoliday(LocalDate.parse("2012-01-01")), is(true));
78+
assertThat(calendar.isHoliday(LocalDate.parse("2026-01-01")), is(true));
79+
80+
// National Sovereignty and Children's Day (Apr 23)
81+
assertThat(calendar.isHoliday(LocalDate.parse("2012-04-23")), is(true));
82+
assertThat(calendar.isHoliday(LocalDate.parse("2013-04-23")), is(true));
83+
assertThat(calendar.isHoliday(LocalDate.parse("2025-04-23")), is(true));
84+
assertThat(calendar.isHoliday(LocalDate.parse("2026-04-23")), is(true));
85+
86+
// Labour Day (May 1)
87+
assertThat(calendar.isHoliday(LocalDate.parse("2012-05-01")), is(true));
88+
assertThat(calendar.isHoliday(LocalDate.parse("2013-05-01")), is(true));
89+
assertThat(calendar.isHoliday(LocalDate.parse("2020-05-01")), is(true));
90+
assertThat(calendar.isHoliday(LocalDate.parse("2025-05-01")), is(true));
91+
assertThat(calendar.isHoliday(LocalDate.parse("2026-05-01")), is(true));
92+
93+
// Youth and Sports Day (May 19)
94+
assertThat(calendar.isHoliday(LocalDate.parse("2012-05-19")), is(true));
95+
assertThat(calendar.isHoliday(LocalDate.parse("2013-05-19")), is(true));
96+
assertThat(calendar.isHoliday(LocalDate.parse("2025-05-19")), is(true));
97+
assertThat(calendar.isHoliday(LocalDate.parse("2026-05-19")), is(true));
98+
99+
// Victory Day (Aug 30)
100+
assertThat(calendar.isHoliday(LocalDate.parse("2012-08-30")), is(true));
101+
assertThat(calendar.isHoliday(LocalDate.parse("2013-08-30")), is(true));
102+
assertThat(calendar.isHoliday(LocalDate.parse("2024-08-30")), is(true));
103+
assertThat(calendar.isHoliday(LocalDate.parse("2025-08-30")), is(true));
104+
}
105+
106+
@Test
107+
public void testDemocracyAndNationalUnityDay()
108+
{
109+
var calendar = TradeCalendarManager.getInstance("bist");
110+
111+
// Democracy and National Unity Day (Jul 15) — introduced in 2017
112+
assertThat(calendar.isHoliday(LocalDate.parse("2016-07-15")), is(false));
113+
assertThat(calendar.isHoliday(LocalDate.parse("2017-07-15")), is(true));
114+
assertThat(calendar.isHoliday(LocalDate.parse("2018-07-15")), is(true));
115+
assertThat(calendar.isHoliday(LocalDate.parse("2024-07-15")), is(true));
116+
assertThat(calendar.isHoliday(LocalDate.parse("2025-07-15")), is(true));
117+
}
118+
119+
@Test
120+
public void testRepublicDay()
121+
{
122+
var calendar = TradeCalendarManager.getInstance("bist");
123+
124+
// Oct 28: half-day in most years, full holiday only in 2012, 2017, 2018
125+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-28")), is(true));
126+
assertThat(calendar.isHoliday(LocalDate.parse("2017-10-28")), is(true));
127+
assertThat(calendar.isHoliday(LocalDate.parse("2018-10-28")), is(true));
128+
assertThat(calendar.isHoliday(LocalDate.parse("2020-10-28")), is(false));
129+
assertThat(calendar.isHoliday(LocalDate.parse("2025-10-28")), is(false));
130+
131+
// Oct 29: full holiday every year
132+
assertThat(calendar.isHoliday(LocalDate.parse("2012-10-29")), is(true));
133+
assertThat(calendar.isHoliday(LocalDate.parse("2013-10-29")), is(true));
134+
assertThat(calendar.isHoliday(LocalDate.parse("2024-10-29")), is(true));
135+
assertThat(calendar.isHoliday(LocalDate.parse("2025-10-29")), is(true));
136+
}
137+
138+
@Test
139+
public void testWeekendsAndWorkingDays()
140+
{
141+
var calendar = TradeCalendarManager.getInstance("bist");
142+
143+
// Standard weekend: Saturday and Sunday are holidays
144+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-04")), is(true)); // Saturday
145+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-05")), is(true)); // Sunday
146+
147+
// Regular weekdays without holidays are working days
148+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-06")), is(false)); // Monday
149+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-07")), is(false)); // Tuesday
150+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-08")), is(false)); // Wednesday
151+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-09")), is(false)); // Thursday
152+
assertThat(calendar.isHoliday(LocalDate.parse("2025-01-10")), is(false)); // Friday
153+
}
154+
}

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/util/TradeCalendarTest.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class TradeCalendarTest
1414
@Test
1515
public void testEasterHolidays()
1616
{
17-
TradeCalendar calendar = TradeCalendarManager.getInstance("de");
17+
var calendar = TradeCalendarManager.getInstance("de");
1818

1919
assertThat(calendar.isHoliday(LocalDate.parse("2015-04-02")), is(false));
2020
assertThat(calendar.isHoliday(LocalDate.parse("2015-04-03")), is(true));
@@ -29,7 +29,7 @@ public void testEasterHolidays()
2929
@Test
3030
public void testWeekends()
3131
{
32-
TradeCalendar calendar = TradeCalendarManager.getInstance("de");
32+
var calendar = TradeCalendarManager.getInstance("de");
3333

3434
assertThat(calendar.isHoliday(LocalDate.parse("2015-01-31")), is(true));
3535
assertThat(calendar.isHoliday(LocalDate.parse("2015-02-01")), is(true));
@@ -38,7 +38,7 @@ public void testWeekends()
3838
@Test
3939
public void testFixedPublicHolidays()
4040
{
41-
TradeCalendar calendar = TradeCalendarManager.getInstance("de");
41+
var calendar = TradeCalendarManager.getInstance("de");
4242

4343
assertThat(calendar.isHoliday(LocalDate.parse("2015-01-01")), is(true));
4444
assertThat(calendar.isHoliday(LocalDate.parse("2015-05-01")), is(true));
@@ -60,7 +60,7 @@ public void testFixedPublicHolidays()
6060
@Test
6161
public void testDayOfRepentanceAndPrayer()
6262
{
63-
TradeCalendar calendar = TradeCalendarManager.getInstance("de");
63+
var calendar = TradeCalendarManager.getInstance("de");
6464

6565
assertThat(calendar.isHoliday(LocalDate.parse("1989-11-22")), is(true)); // latest possible day
6666
assertThat(calendar.isHoliday(LocalDate.parse("1990-11-21")), is(true));
@@ -74,7 +74,7 @@ public void testDayOfRepentanceAndPrayer()
7474
@Test
7575
public void testGermanHolidaysWithTrading()
7676
{
77-
TradeCalendar calendar = TradeCalendarManager.getInstance("de");
77+
var calendar = TradeCalendarManager.getInstance("de");
7878

7979
// Ascension
8080
assertThat(calendar.isHoliday(LocalDate.parse("1999-05-13")), is(true));
@@ -117,7 +117,7 @@ public void testGermanHolidaysWithTrading()
117117
@Test
118118
public void testHolidaysWithCondition()
119119
{
120-
TradeCalendar calendar = TradeCalendarManager.getInstance("nyse");
120+
var calendar = TradeCalendarManager.getInstance("nyse");
121121

122122
assertThat(calendar.isHoliday(LocalDate.parse("2019-10-29")), is(false));
123123
assertThat(calendar.isHoliday(LocalDate.parse("2013-10-29")), is(false));
@@ -129,7 +129,7 @@ public void testHolidaysWithCondition()
129129
@Test
130130
public void testMovingHolidays()
131131
{
132-
TradeCalendar calendar = TradeCalendarManager.getInstance("nyse");
132+
var calendar = TradeCalendarManager.getInstance("nyse");
133133

134134
assertThat(calendar.isHoliday(LocalDate.parse("2018-12-25")), is(true));
135135
assertThat(calendar.isHoliday(LocalDate.parse("2018-12-26")), is(false));
@@ -162,7 +162,7 @@ public void testTradeCalenderNYSE()
162162
{
163163
// See https://www.nyse.com/markets/hours-calendars
164164

165-
TradeCalendar calendar = TradeCalendarManager.getInstance("nyse");
165+
var calendar = TradeCalendarManager.getInstance("nyse");
166166

167167
// New Year
168168
assertThat(calendar.isHoliday(LocalDate.parse("2020-01-01")), is(true));
@@ -242,7 +242,7 @@ public void testTradeCalenderLSE()
242242
{
243243
// See https://www.londonstockexchange.com/trade/trading-access/business-days
244244

245-
TradeCalendar calendar = TradeCalendarManager.getInstance("lse");
245+
var calendar = TradeCalendarManager.getInstance("lse");
246246

247247
// New Year
248248
assertThat(calendar.isHoliday(LocalDate.parse("2019-01-01")), is(true));
@@ -316,7 +316,7 @@ public void testTradeCalenderEuronext()
316316
{
317317
// See https://www.euronext.com/en/trading-calendars-hours
318318

319-
TradeCalendar calendar = TradeCalendarManager.getInstance("euronext");
319+
var calendar = TradeCalendarManager.getInstance("euronext");
320320

321321
// New Year
322322
assertThat(calendar.isHoliday(LocalDate.parse("2019-01-01")), is(true));
@@ -351,7 +351,7 @@ public void testTradeCalenderTSX()
351351
{
352352
// See https://www.tsx.com/trading/calendars-and-trading-hours/calendar
353353

354-
TradeCalendar calendar = TradeCalendarManager.getInstance("tsx");
354+
var calendar = TradeCalendarManager.getInstance("tsx");
355355

356356
// New Year
357357
assertThat(calendar.isHoliday(LocalDate.parse("2011-01-03")), is(true));
@@ -431,7 +431,7 @@ public void testTradeCalenderIBOV()
431431
{
432432
// See https://www.b3.com.br/pt_br/solucoes/plataformas/puma-trading-system/para-participantes-e-traders/calendario-de-negociacao/feriados/
433433

434-
TradeCalendar calendar = TradeCalendarManager.getInstance("ibov");
434+
var calendar = TradeCalendarManager.getInstance("ibov");
435435

436436
// New Year
437437
assertThat(calendar.isHoliday(LocalDate.parse("2023-01-01")), is(true));
@@ -485,7 +485,7 @@ public void testTradeCalenderSIX()
485485
{
486486
// See https://six-group.com/exchanges/exchange_traded_products/trading/trading_and_settlement_calendar_de.html
487487

488-
TradeCalendar calendar = TradeCalendarManager.getInstance("six");
488+
var calendar = TradeCalendarManager.getInstance("six");
489489

490490
// New Year
491491
assertThat(calendar.isHoliday(LocalDate.parse("2023-01-01")), is(true));
@@ -538,7 +538,7 @@ public void testTradeCalenderISE()
538538
{
539539
// See https://www.borsaitaliana.it/borsaitaliana/calendario-e-orari-di-negoziazione/calendario-borsa-orari-di-negoziazione.en.htm
540540

541-
TradeCalendar calendar = TradeCalendarManager.getInstance("ise");
541+
var calendar = TradeCalendarManager.getInstance("ise");
542542

543543
// New Year
544544
assertThat(calendar.isHoliday(LocalDate.parse("2022-12-31")), is(true));
@@ -574,7 +574,7 @@ public void testTradeCalenderVSE()
574574
{
575575
// See https://www.wienerborse.at/handel/handelsinformationen/handelskalender/
576576

577-
TradeCalendar calendar = TradeCalendarManager.getInstance("vse");
577+
var calendar = TradeCalendarManager.getInstance("vse");
578578

579579
// New Year
580580
assertThat(calendar.isHoliday(LocalDate.parse("2022-12-31")), is(true));
@@ -610,7 +610,7 @@ public void testTradeCalenderVSE()
610610
@Test
611611
public void testTradeCalenderMICEXRTS()
612612
{
613-
TradeCalendar calendar = TradeCalendarManager.getInstance("MICEX-RTS");
613+
var calendar = TradeCalendarManager.getInstance("MICEX-RTS");
614614

615615
// New Year
616616
assertThat(calendar.isHoliday(LocalDate.parse("2023-01-01")), is(true));
@@ -661,7 +661,7 @@ public void testTradeCalenderMICEXRTS()
661661
public void testTradeCalendarSSE()
662662
{
663663
// Initialize the trade calendar for "sse"
664-
TradeCalendar calendar = TradeCalendarManager.getInstance("sse");
664+
var calendar = TradeCalendarManager.getInstance("sse");
665665

666666

667667
// Test New Year
@@ -748,13 +748,13 @@ public void testTradeCalendarSSE()
748748
@Test
749749
public void testEmptyCalendar()
750750
{
751-
TradeCalendar calendar = TradeCalendarManager.getInstance("empty");
751+
var calendar = TradeCalendarManager.getInstance("empty");
752752

753753
// we generate a random day
754-
long minDay = LocalDate.of(2000, 1, 1).toEpochDay();
755-
long maxDay = LocalDate.of(2020, 12, 31).toEpochDay();
756-
long randomDay = ThreadLocalRandom.current().nextLong(minDay, maxDay);
757-
LocalDate randomDate = LocalDate.ofEpochDay(randomDay);
754+
var minDay = LocalDate.of(2000, 1, 1).toEpochDay();
755+
var maxDay = LocalDate.of(2020, 12, 31).toEpochDay();
756+
var randomDay = ThreadLocalRandom.current().nextLong(minDay, maxDay);
757+
var randomDate = LocalDate.ofEpochDay(randomDay);
758758

759759
// in the empty calendar, every day is a (potential) trading day
760760
assertThat(calendar.isHoliday(randomDate), is(false));

name.abuchen.portfolio/src/name/abuchen/portfolio/Messages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ public class Messages extends NLS
213213
public static String LabelSuffixEntryCorrected;
214214
public static String LabelSum;
215215
public static String LabelTradeCalendarASX;
216+
public static String LabelTradeCalendarBIST;
216217
public static String LabelTradeCalendarDefault;
217218
public static String LabelTradeCalendarEmpty;
218219
public static String LabelTradeCalendarEuronext;

name.abuchen.portfolio/src/name/abuchen/portfolio/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ LabelSum = Sum:
414414

415415
LabelTradeCalendarASX = Australian Stock Exchange (ASX)
416416

417+
LabelTradeCalendarBIST = Borsa Istanbul
418+
417419
LabelTradeCalendarDefault = Default calendar
418420

419421
LabelTradeCalendarEmpty = (None)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_ca.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ LabelSum = Total:
413413
414414
LabelTradeCalendarASX = Borsa d'Austr\u00E0lia (ASX)
415415

416+
LabelTradeCalendarBIST = Borsa d'Istanbul
417+
416418
LabelTradeCalendarDefault = Calendari est\u00E0ndard
417419
418420
LabelTradeCalendarEmpty = (Cap)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_cs.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ LabelSum = Suma:
414414

415415
LabelTradeCalendarASX = Australsk\u00E1 burza cenn\u00FDch pap\u00EDr\u016F (ASX)
416416

417+
LabelTradeCalendarBIST = Istanbulsk\u00E1 burza
418+
417419
LabelTradeCalendarDefault = V\u00FDchoz\u00ED kalend\u00E1\u0159
418420

419421
LabelTradeCalendarEmpty = (Neexistuje)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_da.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ LabelSum = Sum:
414414

415415
LabelTradeCalendarASX = Den australske b\u00F8rs (ASX)
416416

417+
LabelTradeCalendarBIST = Istanbul B\u00F8rsen
418+
417419
LabelTradeCalendarDefault = Standard kalender
418420

419421
LabelTradeCalendarEmpty = (Ingen)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_de.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ LabelSum = Summe:
414414

415415
LabelTradeCalendarASX = Australische B\u00F6rse (ASX)
416416

417+
LabelTradeCalendarBIST = Istanbuler B\u00F6rse
418+
417419
LabelTradeCalendarDefault = Standardkalender
418420

419421
LabelTradeCalendarEmpty = (Keiner)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_es.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ LabelSum = Total:
414414

415415
LabelTradeCalendarASX = Bolsa de Australia (ASX)
416416

417+
LabelTradeCalendarBIST = Bolsa de Estambul
418+
417419
LabelTradeCalendarDefault = Calendario est\u00E1ndar
418420

419421
LabelTradeCalendarEmpty = (Ninguno)

name.abuchen.portfolio/src/name/abuchen/portfolio/messages_fi.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ LabelSum = Summa:
413413

414414
LabelTradeCalendarASX = Australian p\u00F6rssi (ASX)
415415

416+
LabelTradeCalendarBIST = Istanbulin p\u00F6rssi
417+
416418
LabelTradeCalendarDefault = Oletuskalenteri
417419

418420
LabelTradeCalendarEmpty = (Ei mit\u00E4\u00E4n)

0 commit comments

Comments
 (0)